Merge commit 'b35b94a6e74a8e3422e555fe0c171dbab119fe94' into beta

This commit is contained in:
Ali 2019-11-06 16:48:52 +04:00
commit 978765b720
33 changed files with 5336 additions and 4397 deletions

View File

@ -5075,5 +5075,13 @@ Any member of this group will be able to see messages in the channel.";
"Group.ErrorSupergroupConversionNotPossible" = "Sorry, you are a member of too many groups and channels. Please leave some before creating a new one.";
"ClearCache.StorageTitle" = "%@ STORAGE";
"ClearCache.StorageCache" = "Telegram Cache";
"ClearCache.StorageServiceFiles" = "Telegram Service Files";
"ClearCache.StorageOtherApps" = "Other Apps";
"ClearCache.StorageFree" = "Free";
"ClearCache.ClearCache" = "Clear Telegram Cache";
"ClearCache.Clear" = "Clear";
"ChatList.DeletedChats_1" = "Deleted 1 chat";
"ChatList.DeletedChats_any" = "Deleted %@ chats";

View File

@ -402,10 +402,19 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
}
func authorizeAccessToCamera(completion: @escaping () -> Void) {
AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in
AVCaptureDevice.requestAccess(for: AVMediaType.video) { [weak self] response in
guard let strongSelf = self else {
return
}
Queue.mainQueue().async {
if response {
completion()
} else {
let presentationData = strongSelf.presentationData
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.Wallet_AccessDenied_Title, text: presentationData.strings.Wallet_AccessDenied_Camera, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Wallet_Intro_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Wallet_AccessDenied_Settings, action: {
strongSelf.openPlatformSettings()
})])
strongSelf.window.present(controller, on: .root)
}
}
}
@ -540,7 +549,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
let presentationData = WalletPresentationData(
theme: WalletTheme(
info: WalletInfoTheme(
buttonBackgroundColor: accentColor,
buttonBackgroundColor: UIColor(rgb: 0x32aafe),
buttonTextColor: .white,
incomingFundsTitleColor: UIColor(rgb: 0x00b12c),
outgoingFundsTitleColor: UIColor(rgb: 0xff3b30)

View File

@ -218,3 +218,6 @@
"Wallet.Time.PreciseDate_m12" = "Dec %1$@, %2$@ at %3$@";
"Wallet.VoiceOver.Editing.ClearText" = "Clear text";
"Wallet.Receive.ShareInvoiceUrlInfo" = "Share this link with other Gram wallet owners to receive %@ Grams from them.";
"Wallet.AccessDenied.Title" = "Please Allow Access";
"Wallet.AccessDenied.Camera" = "TON Wallet needs access to camera to scan QR codes.\n\nPlease go to Settings > Privacy > Camera and set TON Wallet to ON.";
"Wallet.AccessDenied.Settings" = "Settings";

View File

@ -763,7 +763,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
strongSelf.labelArrowNode = updatedLabelArrowNode
strongSelf.containerNode.addSubnode(updatedLabelArrowNode)
if let image = updatedLabelArrowNode.image {
let labelArrowNodeFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightLabelInset - image.size.width, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
let labelArrowNodeFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightLabelInset - image.size.width + 8.0, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
transition.updateFrame(node: updatedLabelArrowNode, frame: labelArrowNodeFrame)
rightLabelInset += 19.0
}
@ -775,10 +775,10 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
let badgeWidth = max(badgeDiameter, labelLayout.size.width + 10.0)
let labelFrame: CGRect
if case .badge = item.label {
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: floor((contentSize.height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame
} else {
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - rightLabelInset - rightInset, y: floor((contentSize.height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - rightLabelInset - rightInset, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame)
}

View File

@ -45,7 +45,7 @@ typedef enum {
@property (nonatomic, strong) NSString *recipientName;
@property (nonatomic, copy) void(^finishedWithResults)(TGOverlayController *controller, TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem);
@property (nonatomic, copy) void(^finishedWithResults)(TGOverlayController *controller, TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem, bool silentPosting, int32_t scheduleTime);
@property (nonatomic, copy) void(^finishedWithPhoto)(TGOverlayController *controller, UIImage *resultImage, NSString *caption, NSArray *entities, NSArray *stickers, NSNumber *timer);
@property (nonatomic, copy) void(^finishedWithVideo)(TGOverlayController *controller, NSURL *videoURL, UIImage *previewImage, NSTimeInterval duration, CGSize dimensions, TGVideoEditAdjustments *adjustments, NSString *caption, NSArray *entities, NSArray *stickers, NSNumber *timer);
@ -55,6 +55,8 @@ typedef enum {
@property (nonatomic, copy) void(^finishedTransitionOut)(void);
@property (nonatomic, copy) void(^customPresentOverlayController)(TGOverlayController *(^)(id<LegacyComponentsContext>));
@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t));
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia intent:(TGCameraControllerIntent)intent;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia camera:(PGCamera *)camera previewView:(TGCameraPreviewView *)previewView intent:(TGCameraControllerIntent)intent;

View File

@ -45,6 +45,7 @@
#import <LegacyComponents/TGTimerTarget.h>
#import <LegacyComponents/TGMenuSheetController.h>
#import <LegacyComponents/TGMediaPickerSendActionSheetController.h>
#import "TGMediaPickerGallerySelectedItemsModel.h"
#import "TGCameraCapturedPhoto.h"
@ -1251,6 +1252,128 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
model.controller = galleryController;
model.suggestionContext = self.suggestionContext;
__weak TGModernGalleryController *weakGalleryController = galleryController;
__weak TGMediaPickerGalleryModel *weakModel = model;
model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item) {
__strong TGCameraController *strongSelf = weakSelf;
__strong TGMediaPickerGalleryModel *strongModel = weakModel;
if (strongSelf == nil || !(strongSelf.hasSilentPosting || strongSelf.hasSchedule) || strongSelf->_shortcut)
return;
if (iosMajorVersion() >= 10) {
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
[generator impactOccurred];
}
bool effectiveHasSchedule = strongSelf.hasSchedule;
for (id item in strongModel.selectionContext.selectedItems)
{
if ([item isKindOfClass:[TGMediaAsset class]])
{
if ([[strongSelf->_editingContext timerForItem:item] integerValue] > 0)
{
effectiveHasSchedule = false;
break;
}
}
}
TGMediaPickerSendActionSheetController *controller = [[TGMediaPickerSendActionSheetController alloc] initWithContext:strongSelf->_context sendButtonFrame:strongModel.interfaceView.doneButtonFrame canSendSilently:strongSelf->_hasSilentPosting canSchedule:effectiveHasSchedule];
controller.send = ^{
__strong TGCameraController *strongSelf = weakSelf;
__strong TGMediaPickerGalleryModel *strongModel = weakModel;
if (strongSelf == nil || strongModel == nil)
return;
__strong TGModernGalleryController *strongController = weakGalleryController;
if (strongController == nil)
return;
if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
{
TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
[itemView stop];
[itemView setPlayButtonHidden:true animated:true];
}
if (strongSelf->_selectionContext.allowGrouping)
[[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
if (strongSelf.finishedWithResults != nil)
strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, 0);
[strongSelf _dismissTransitionForResultController:strongController];
};
controller.sendSilently = ^{
__strong TGCameraController *strongSelf = weakSelf;
__strong TGMediaPickerGalleryModel *strongModel = weakModel;
if (strongSelf == nil || strongModel == nil)
return;
__strong TGModernGalleryController *strongController = weakGalleryController;
if (strongController == nil)
return;
if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
{
TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
[itemView stop];
[itemView setPlayButtonHidden:true animated:true];
}
if (strongSelf->_selectionContext.allowGrouping)
[[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
if (strongSelf.finishedWithResults != nil)
strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, true, 0);
[strongSelf _dismissTransitionForResultController:strongController];
};
controller.schedule = ^{
__strong TGCameraController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf.presentScheduleController(^(int32_t time) {
__strong TGCameraController *strongSelf = weakSelf;
__strong TGMediaPickerGalleryModel *strongModel = weakModel;
if (strongSelf == nil || strongModel == nil)
return;
__strong TGModernGalleryController *strongController = weakGalleryController;
if (strongController == nil)
return;
if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
{
TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
[itemView stop];
[itemView setPlayButtonHidden:true animated:true];
}
if (strongSelf->_selectionContext.allowGrouping)
[[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
if (strongSelf.finishedWithResults != nil)
strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, time);
[strongSelf _dismissTransitionForResultController:strongController];
});
};
id<LegacyComponentsOverlayWindowManager> windowManager = nil;
id<LegacyComponentsContext> windowContext = nil;
windowManager = [strongSelf->_context makeOverlayWindowManager];
windowContext = [windowManager context];
TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:strongSelf contentController:(TGOverlayController *)controller];
controllerWindow.hidden = false;
};
model.willFinishEditingItem = ^(id<TGMediaEditableItem> editableItem, id<TGMediaEditAdjustments> adjustments, id representation, bool hasChanges)
{
__strong TGCameraController *strongSelf = weakSelf;
@ -1278,9 +1401,6 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
model.interfaceView.hasSwipeGesture = false;
galleryController.model = model;
__weak TGModernGalleryController *weakGalleryController = galleryController;
__weak TGMediaPickerGalleryModel *weakModel = model;
if (_items.count > 1)
[model.interfaceView updateSelectionInterface:selectionContext.count counterVisible:(selectionContext.count > 0) animated:false];
@ -1318,7 +1438,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
[[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
if (strongSelf.finishedWithResults != nil)
strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset);
strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, 0);
if (strongSelf->_shortcut)
return;

View File

@ -135,11 +135,19 @@
- (NSString *)fileName
{
if (self.backingAsset != nil)
return [self.backingAsset valueForKey:@"filename"];
else if (self.backingLegacyAsset != nil)
if (self.backingAsset != nil) {
NSString *fileName = [self.backingAsset valueForKey:@"filename"];
if (fileName == nil) {
NSArray *resources = [PHAssetResource assetResourcesForAsset:self.backingAsset];
PHAssetResource *resource = resources.firstObject;
if (resource != nil) {
fileName = resource.originalFilename;
}
}
return fileName;
} else if (self.backingLegacyAsset != nil) {
return self.backingLegacyAsset.defaultRepresentation.filename;
}
return nil;
}

View File

@ -295,6 +295,9 @@
if (!found)
fileName = asset.fileName;
}
if (fileName == nil) {
fileName = asset.fileName;
}
if (iosMajorVersion() >= 10 && [dataUTI rangeOfString:@"heic"].location != NSNotFound)
{

View File

@ -1,5 +1,7 @@
#import "TGMediaPickerModernGalleryMixin.h"
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/LegacyComponents.h>
#import <LegacyComponents/TGModernGalleryController.h>
@ -144,8 +146,10 @@
if (strongSelf == nil || !(hasSilentPosting || hasSchedule))
return;
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
[generator impactOccurred];
if (iosMajorVersion() >= 10) {
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
[generator impactOccurred];
}
bool effectiveHasSchedule = hasSchedule;
for (id item in strongSelf->_galleryModel.selectionContext.selectedItems)

View File

@ -382,7 +382,7 @@
[strongCameraView attachPreviewViewAnimated:true];
};
controller.finishedWithResults = ^(__unused TGOverlayController *controller, TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem)
controller.finishedWithResults = ^(__unused TGOverlayController *controller, TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem, __unused bool silentPosting, __unused int32_t scheduleTime)
{
__strong TGMenuSheetController *strongMenuController = weakMenuController;
if (strongMenuController == nil)

View File

@ -148,33 +148,35 @@ class ItemListCallListItemNode: ListViewItemNode {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let currentItem = self.item
return { item, params, neighbors in
if self.callNodes.count != item.messages.count {
for pair in self.callNodes {
return { [weak self] item, params, neighbors in
if let strongSelf = self, strongSelf.callNodes.count != item.messages.count {
for pair in strongSelf.callNodes {
pair.0.removeFromSupernode()
pair.1.removeFromSupernode()
}
self.callNodes = []
strongSelf.callNodes = []
for _ in item.messages {
let timeNode = TextNode()
timeNode.isUserInteractionEnabled = false
self.addSubnode(timeNode)
strongSelf.addSubnode(timeNode)
let typeNode = TextNode()
typeNode.isUserInteractionEnabled = false
self.addSubnode(typeNode)
strongSelf.addSubnode(typeNode)
self.callNodes.append((timeNode, typeNode))
strongSelf.callNodes.append((timeNode, typeNode))
}
}
var makeNodesLayout: [((TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode))] = []
for nodes in self.callNodes {
let makeTimeLayout = TextNode.asyncLayout(nodes.0)
let makeTypeLayout = TextNode.asyncLayout(nodes.1)
makeNodesLayout.append((makeTimeLayout, makeTypeLayout))
if let strongSelf = self {
for nodes in strongSelf.callNodes {
let makeTimeLayout = TextNode.asyncLayout(nodes.0)
let makeTypeLayout = TextNode.asyncLayout(nodes.1)
makeNodesLayout.append((makeTimeLayout, makeTypeLayout))
}
}
var updatedTheme: PresentationTheme?

View File

@ -31,7 +31,7 @@ enum AutomaticDownloadDataUsage: Int {
}
}
class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem {
final class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let strings: PresentationStrings
let value: AutomaticDownloadDataUsage
@ -93,7 +93,7 @@ private func generateKnobImage() -> UIImage? {
})
}
class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode

View File

@ -49,7 +49,7 @@ private func sizeValue(for sliderValue: CGFloat) -> Int32 {
return 0
}
class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
final class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let decimalSeparator: String
let text: String
@ -109,7 +109,7 @@ private func generateKnobImage() -> UIImage? {
})
}
class AutodownloadSizeLimitItemNode: ListViewItemNode {
private final class AutodownloadSizeLimitItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode

View File

@ -8,7 +8,7 @@ import ItemListUI
import PresentationDataUtils
import ActivityIndicator
class CalculatingCacheSizeItem: ListViewItem, ItemListItem {
final class CalculatingCacheSizeItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let title: String
let sectionId: ItemListSectionId
@ -57,7 +57,7 @@ class CalculatingCacheSizeItem: ListViewItem, ItemListItem {
private let titleFont = Font.regular(14.0)
class CalculatingCacheSizeItemNode: ListViewItemNode {
private final class CalculatingCacheSizeItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode

View File

@ -485,6 +485,9 @@ func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndSt
let actionsDisposable = DisposableSet()
let cacheUsagePromise = Promise<CacheUsageStatsResult?>()
cacheUsagePromise.set(cacheUsageStats(context: context))
let dataAndStorageDataPromise = Promise<DataAndStorageData>()
dataAndStorageDataPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings, ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings, ApplicationSpecificSharedDataKeys.voiceCallSettings, SharedDataKeys.proxySettings])
|> map { sharedData -> DataAndStorageData in
@ -526,7 +529,7 @@ func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndSt
})
let arguments = DataAndStorageControllerArguments(openStorageUsage: {
pushControllerImpl?(storageUsageController(context: context))
pushControllerImpl?(storageUsageController(context: context, cacheUsagePromise: cacheUsagePromise))
}, openNetworkUsage: {
pushControllerImpl?(networkUsageStatsController(context: context))
}, openProxy: {

View File

@ -0,0 +1,315 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
import SyncCore
import TelegramUIPreferences
import TelegramPresentationData
import LegacyComponents
import ItemListUI
import PresentationDataUtils
private func stringForKeepMediaTimeout(strings: PresentationStrings, timeout: Int32) -> String {
if timeout > 1 * 31 * 24 * 60 * 60 {
return strings.MessageTimer_Forever
} else {
return timeIntervalString(strings: strings, value: timeout)
}
}
private let keepMediaTimeoutValues: [Int32] = [
3 * 24 * 60 * 60,
7 * 24 * 60 * 60,
1 * 31 * 24 * 60 * 60,
Int32.max
]
final class KeepMediaDurationPickerItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let strings: PresentationStrings
let value: Int32
let sectionId: ItemListSectionId
let updated: (Int32) -> Void
init(theme: PresentationTheme, strings: PresentationStrings, value: Int32, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) {
self.theme = theme
self.strings = strings
self.value = value
self.sectionId = sectionId
self.updated = updated
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = KeepMediaDurationPickerItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? KeepMediaDurationPickerItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
}
private func generateKnobImage() -> UIImage? {
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setShadow(offset: CGSize(width: 0.0, height: -2.0), blur: 3.5, color: UIColor(white: 0.0, alpha: 0.35).cgColor)
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0)))
})
}
private final class KeepMediaDurationPickerItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
private let textNodes: [TextNode]
private var sliderView: TGPhotoEditorSliderView?
private var item: KeepMediaDurationPickerItem?
private var layoutParams: ListViewItemLayoutParams?
init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.maskNode = ASImageNode()
var textNodes: [TextNode] = []
for i in 0 ..< 4 {
let textNode = TextNode()
textNode.isUserInteractionEnabled = false
textNode.displaysAsynchronously = false
textNodes.append(textNode)
}
self.textNodes = textNodes
super.init(layerBacked: false, dynamicBounce: false)
for textNode in textNodes {
self.addSubnode(textNode)
}
}
func updateSliderView() {
if let sliderView = self.sliderView, let item = self.item {
sliderView.maximumValue = 3.0
sliderView.positionsCount = 4
let value = keepMediaTimeoutValues.firstIndex(where: { $0 == item.value }) ?? 0
sliderView.value = CGFloat(value)
}
}
override func didLoad() {
super.didLoad()
let sliderView = TGPhotoEditorSliderView()
sliderView.enablePanHandling = true
sliderView.trackCornerRadius = 1.0
sliderView.lineSize = 2.0
sliderView.dotSize = 5.0
sliderView.minimumValue = 0.0
sliderView.maximumValue = 3.0
sliderView.startValue = 0.0
sliderView.disablesInteractiveTransitionGestureRecognizer = true
sliderView.positionsCount = 4
sliderView.useLinesForPositions = true
if let item = self.item, let params = self.layoutParams {
let value = keepMediaTimeoutValues.firstIndex(where: { $0 == item.value }) ?? 0
sliderView.value = CGFloat(value)
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
sliderView.backColor = item.theme.list.disclosureArrowColor
sliderView.startColor = item.theme.list.disclosureArrowColor
sliderView.trackColor = item.theme.list.itemAccentColor
sliderView.knobImage = generateKnobImage()
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0))
sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX)
}
self.view.addSubview(sliderView)
sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged)
self.sliderView = sliderView
self.updateSliderView()
}
func asyncLayout() -> (_ item: KeepMediaDurationPickerItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item
var makeTextLayouts: [(TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode)] = []
for textNode in self.textNodes {
makeTextLayouts.append(TextNode.asyncLayout(textNode))
}
return { item, params, neighbors in
var themeUpdated = false
if currentItem?.theme !== item.theme {
themeUpdated = true
}
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
var textLayouts: [TextNodeLayout] = []
var textApplies: [() -> TextNode] = []
for i in 0 ..< makeTextLayouts.count {
let makeTextLayout = makeTextLayouts[i]
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: stringForKeepMediaTimeout(strings: item.strings, timeout: keepMediaTimeoutValues[i]), font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
textLayouts.append(textLayout)
textApplies.append(textApply)
}
contentSize = CGSize(width: params.width, height: 88.0)
insets = itemListNeighborsGroupedInsets(neighbors)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.item = item
strongSelf.layoutParams = params
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = 0.0
bottomStripeOffset = -separatorHeight
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
for apply in textApplies {
let _ = apply()
}
var textNodes: [(TextNode, CGSize)] = []
for (node, size) in zip(strongSelf.textNodes, textLayouts.map { $0.size }) {
textNodes.append((node, size))
}
let delta = (params.width - params.leftInset - params.rightInset - 18.0 * 2.0) / CGFloat(textNodes.count - 1)
for i in 0 ..< textNodes.count {
let (textNode, textSize) = textNodes[i]
var position = params.leftInset + 18.0 + delta * CGFloat(i)
if i == textNodes.count - 1 {
position -= textSize.width
} else if i > 0 {
position -= textSize.width / 2.0
}
textNode.frame = CGRect(origin: CGPoint(x: position, y: 15.0), size: textSize)
}
if let sliderView = strongSelf.sliderView {
if themeUpdated {
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
sliderView.backColor = item.theme.list.disclosureArrowColor
sliderView.trackColor = item.theme.list.itemAccentColor
sliderView.knobImage = generateKnobImage()
}
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0))
sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX)
strongSelf.updateSliderView()
}
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
@objc private func sliderValueChanged() {
guard let sliderView = self.sliderView else {
return
}
let position = Int(sliderView.value)
let value = keepMediaTimeoutValues[position]
self.item?.updated(value)
}
}

View File

@ -12,7 +12,7 @@ enum ProxySettingsActionIcon {
case add
}
class ProxySettingsActionItem: ListViewItem, ItemListItem {
final class ProxySettingsActionItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let title: String
let icon: ProxySettingsActionIcon
@ -77,7 +77,7 @@ class ProxySettingsActionItem: ListViewItem, ItemListItem {
private let titleFont = Font.regular(17.0)
class ProxySettingsActionItemNode: ListViewItemNode {
private final class ProxySettingsActionItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode

View File

@ -101,7 +101,7 @@ final class ProxySettingsServerItem: ListViewItem, ItemListItem {
private let titleFont = Font.regular(17.0)
private let statusFont = Font.regular(14.0)
class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode

View File

@ -7,6 +7,7 @@ import TelegramCore
import SyncCore
import TelegramPresentationData
import TelegramUIPreferences
import TelegramStringFormatting
import ItemListUI
import PresentationDataUtils
import OverlayStatusController
@ -15,48 +16,67 @@ import ItemListPeerItem
import DeleteChatPeerActionSheetItem
import UndoUI
private func totalDiskSpace() -> Int64 {
do {
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
return (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? 0
} catch {
return 0
}
}
private func freeDiskSpace() -> Int64 {
do {
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
return (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0
} catch {
return 0
}
}
private final class StorageUsageControllerArguments {
let account: Account
let updateKeepMedia: () -> Void
let updateKeepMediaTimeout: (Int32) -> Void
let openClearAll: () -> Void
let openPeerMedia: (PeerId) -> Void
let clearPeerMedia: (PeerId) -> Void
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
init(account: Account, updateKeepMedia: @escaping () -> Void, openClearAll: @escaping () -> Void, openPeerMedia: @escaping (PeerId) -> Void) {
init(account: Account, updateKeepMediaTimeout: @escaping (Int32) -> Void, openClearAll: @escaping () -> Void, openPeerMedia: @escaping (PeerId) -> Void, clearPeerMedia: @escaping (PeerId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) {
self.account = account
self.updateKeepMedia = updateKeepMedia
self.updateKeepMediaTimeout = updateKeepMediaTimeout
self.openClearAll = openClearAll
self.openPeerMedia = openPeerMedia
self.clearPeerMedia = clearPeerMedia
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
}
}
private enum StorageUsageSection: Int32 {
case keepMedia
case immutableSize
case all
case storage
case peers
}
private enum StorageUsageEntry: ItemListNodeEntry {
case keepMedia(PresentationTheme, String, String)
case keepMediaHeader(PresentationTheme, String)
case keepMedia(PresentationTheme, PresentationStrings, Int32)
case keepMediaInfo(PresentationTheme, String)
case storageHeader(PresentationTheme, String)
case storageUsage(PresentationTheme, PresentationDateTimeFormat, [StorageUsageCategory])
case collecting(PresentationTheme, String)
case immutableSize(PresentationTheme, String, String)
case clearAll(PresentationTheme, String, String, Bool)
case clearAll(PresentationTheme, String, Bool)
case peersHeader(PresentationTheme, String)
case peer(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, Peer?, String)
case peer(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, Peer?, String, Bool)
var section: ItemListSectionId {
switch self {
case .keepMedia, .keepMediaInfo:
case .keepMediaHeader, .keepMedia, .keepMediaInfo:
return StorageUsageSection.keepMedia.rawValue
case .immutableSize:
return StorageUsageSection.immutableSize.rawValue
case .collecting, .clearAll:
return StorageUsageSection.all.rawValue
case .storageHeader, .storageUsage, .collecting, .clearAll:
return StorageUsageSection.storage.rawValue
case .peersHeader, .peer:
return StorageUsageSection.peers.rawValue
}
@ -64,27 +84,37 @@ private enum StorageUsageEntry: ItemListNodeEntry {
var stableId: Int32 {
switch self {
case .keepMedia:
case .keepMediaHeader:
return 0
case .keepMediaInfo:
case .keepMedia:
return 1
case .collecting:
case .keepMediaInfo:
return 2
case .immutableSize:
case .storageHeader:
return 3
case .clearAll:
case .storageUsage:
return 4
case .peersHeader:
case .collecting:
return 5
case let .peer(index, _, _, _, _, _, _, _):
return 6 + index
case .clearAll:
return 6
case .peersHeader:
return 7
case let .peer(index, _, _, _, _, _, _, _, _):
return 8 + index
}
}
static func ==(lhs: StorageUsageEntry, rhs: StorageUsageEntry) -> Bool {
switch lhs {
case let .keepMedia(lhsTheme, lhsText, lhsValue):
if case let .keepMedia(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
case let .keepMediaHeader(lhsTheme, lhsText):
if case let .keepMediaHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .keepMedia(lhsTheme, lhsStrings, lhsValue):
if case let .keepMedia(rhsTheme, rhsStrings, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsValue == rhsValue {
return true
} else {
return false
@ -95,20 +125,26 @@ private enum StorageUsageEntry: ItemListNodeEntry {
} else {
return false
}
case let .storageHeader(lhsTheme, lhsText):
if case let .storageHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .storageUsage(lhsTheme, lhsDateTimeFormat, lhsCategories):
if case let .storageUsage(rhsTheme, rhsDateTimeFormat, rhsCategories) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsCategories == rhsCategories {
return true
} else {
return false
}
case let .collecting(lhsTheme, lhsText):
if case let .collecting(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .immutableSize(lhsTheme, lhsText, lhsValue):
if case let .immutableSize(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .clearAll(lhsTheme, lhsText, lhsValue, lhsEnabled):
if case let .clearAll(rhsTheme, rhsText, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
case let .clearAll(lhsTheme, lhsText, lhsEnabled):
if case let .clearAll(rhsTheme, rhsText, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled {
return true
} else {
return false
@ -119,8 +155,8 @@ private enum StorageUsageEntry: ItemListNodeEntry {
} else {
return false
}
case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsChatPeer, lhsValue):
if case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsChatPeer, rhsValue) = rhs {
case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsChatPeer, lhsValue, lhsRevealed):
if case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsChatPeer, rhsValue, rhsRevealed) = rhs {
if lhsIndex != rhsIndex {
return false
}
@ -145,6 +181,9 @@ private enum StorageUsageEntry: ItemListNodeEntry {
if lhsValue != rhsValue {
return false
}
if lhsRevealed != rhsRevealed {
return false
}
return true
} else {
return false
@ -159,54 +198,62 @@ private enum StorageUsageEntry: ItemListNodeEntry {
func item(_ arguments: Any) -> ListViewItem {
let arguments = arguments as! StorageUsageControllerArguments
switch self {
case let .keepMedia(theme, text, value):
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.updateKeepMedia()
})
case let .keepMediaHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .keepMedia(theme, strings, value):
return KeepMediaDurationPickerItem(theme: theme, strings: strings, value: value, sectionId: self.section, updated: { updatedValue in
arguments.updateKeepMediaTimeout(updatedValue)
})
case let .keepMediaInfo(theme, text):
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section)
case let .storageHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .storageUsage(theme, dateTimeFormat, categories):
return StorageUsageItem(theme: theme, dateTimeFormat: dateTimeFormat, categories: categories, sectionId: self.section)
case let .collecting(theme, text):
return CalculatingCacheSizeItem(theme: theme, title: text, sectionId: self.section, style: .blocks)
case let .immutableSize(theme, title, value):
return ItemListDisclosureItem(theme: theme, icon: nil, title: title, enabled: false, titleColor: .primary, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: nil)
case let .clearAll(theme, text, enabled):
return ItemListActionItem(theme: theme, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: {
if enabled {
arguments.openClearAll()
}
})
case let .peersHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .clearAll(theme, text, value, enabled):
return ItemListDisclosureItem(theme: theme, icon: nil, title: text, enabled: enabled, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openClearAll()
})
case let .peer(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, chatPeer, value):
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, aliasHandling: .threatSelfAsSaved, nameColor: chatPeer == nil ? .primary : .secret, presence: nil, text: .none, label: .disclosure(value), editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
case let .peer(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, chatPeer, value, revealed):
var options: [ItemListPeerItemRevealOption] = [ItemListPeerItemRevealOption(type: .destructive, title: strings.ClearCache_Clear, action: {
arguments.clearPeerMedia(peer.id)
})]
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, aliasHandling: .threatSelfAsSaved, nameColor: chatPeer == nil ? .primary : .secret, presence: nil, text: .none, label: .disclosure(value), editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
let resolvedPeer = chatPeer ?? peer
arguments.openPeerMedia(resolvedPeer.id)
}, setPeerIdWithRevealedOptions: { previousId, id in
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setPeerIdWithRevealedOptions(peerId, fromPeerId)
}, removePeer: { _ in
})
}
}
}
private func stringForKeepMediaTimeout(strings: PresentationStrings, timeout: Int32) -> String {
if timeout > 1 * 31 * 24 * 60 * 60 {
return strings.MessageTimer_Forever
} else {
return timeIntervalString(strings: strings, value: timeout)
private struct StoragUsageState: Equatable {
let peerIdWithRevealedOptions: PeerId?
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> StoragUsageState {
return StoragUsageState(peerIdWithRevealedOptions: peerIdWithRevealedOptions)
}
}
private func storageUsageControllerEntries(presentationData: PresentationData, cacheSettings: CacheStorageSettings, cacheStats: CacheUsageStatsResult?) -> [StorageUsageEntry] {
private func storageUsageControllerEntries(presentationData: PresentationData, cacheSettings: CacheStorageSettings, cacheStats: CacheUsageStatsResult?, state: StoragUsageState) -> [StorageUsageEntry] {
var entries: [StorageUsageEntry] = []
entries.append(.keepMedia(presentationData.theme, presentationData.strings.Cache_KeepMedia, stringForKeepMediaTimeout(strings: presentationData.strings, timeout: cacheSettings.defaultCacheStorageTimeout)))
entries.append(.keepMediaHeader(presentationData.theme, presentationData.strings.Cache_KeepMedia.uppercased()))
entries.append(.keepMedia(presentationData.theme, presentationData.strings, cacheSettings.defaultCacheStorageTimeout))
entries.append(.keepMediaInfo(presentationData.theme, presentationData.strings.Cache_Help))
var addedHeader = false
entries.append(.storageHeader(presentationData.theme, presentationData.strings.ClearCache_StorageTitle(stringForDeviceType().uppercased()).0))
if let cacheStats = cacheStats, case let .result(stats) = cacheStats {
entries.append(.immutableSize(presentationData.theme, presentationData.strings.Cache_ServiceFiles, dataSizeString(stats.immutableSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
var peerSizes: Int64 = 0
var statsByPeerId: [(PeerId, Int64)] = []
var peerIndices: [PeerId: Int] = [:]
@ -230,9 +277,27 @@ private func storageUsageControllerEntries(presentationData: PresentationData, c
peerSizes += combinedSize
}
let totalSize = Int64(peerSizes + stats.otherSize + stats.cacheSize + stats.tempSize)
let telegramCacheSize = Int64(peerSizes + stats.otherSize + stats.cacheSize + stats.tempSize)
let totalTelegramSize = telegramCacheSize + stats.immutableSize
entries.append(.clearAll(presentationData.theme, presentationData.strings.Cache_ClearCache, totalSize > 0 ? dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) : presentationData.strings.Cache_ClearEmpty, totalSize > 0))
var categories: [StorageUsageCategory] = []
let totalSpace = max(totalDiskSpace(), 1)
let freeSpace = freeDiskSpace()
let otherAppsSpace = totalSpace - freeSpace - totalTelegramSize
let totalSpaceValue = CGFloat(totalSpace)
if telegramCacheSize > 0 {
categories.append(StorageUsageCategory(title: presentationData.strings.ClearCache_StorageCache, size: totalTelegramSize, fraction: CGFloat(totalTelegramSize) / totalSpaceValue, color: presentationData.theme.list.itemAccentColor))
} else {
categories.append(StorageUsageCategory(title: presentationData.strings.ClearCache_StorageServiceFiles, size: totalTelegramSize, fraction: CGFloat(totalTelegramSize) / totalSpaceValue, color: presentationData.theme.list.itemAccentColor))
}
categories.append(StorageUsageCategory(title: presentationData.strings.ClearCache_StorageOtherApps, size: otherAppsSpace, fraction: CGFloat(otherAppsSpace) / totalSpaceValue, color: presentationData.theme.list.itemBlocksSeparatorColor))
categories.append(StorageUsageCategory(title: presentationData.strings.ClearCache_StorageFree, size: freeSpace, fraction: CGFloat(freeSpace) / totalSpaceValue, color: UIColor(rgb: 0xf2f1f7)))
entries.append(.storageUsage(presentationData.theme, presentationData.dateTimeFormat, categories))
entries.append(.clearAll(presentationData.theme, presentationData.strings.ClearCache_ClearCache, telegramCacheSize > 0))
var index: Int32 = 0
for (peerId, size) in statsByPeerId.sorted(by: { $0.1 > $1.1 }) {
@ -248,7 +313,7 @@ private func storageUsageControllerEntries(presentationData: PresentationData, c
chatPeer = mainPeer
mainPeer = associatedPeer
}
entries.append(.peer(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, mainPeer, chatPeer, dataSizeString(size, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
entries.append(.peer(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, mainPeer, chatPeer, dataSizeString(size, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator), state.peerIdWithRevealedOptions == peer.id))
index += 1
}
}
@ -273,7 +338,28 @@ private func stringForCategory(strings: PresentationStrings, category: PeerCache
}
}
public func storageUsageController(context: AccountContext, isModal: Bool = false) -> ViewController {
func cacheUsageStats(context: AccountContext) -> Signal<CacheUsageStatsResult?, NoError> {
let containerPath = context.sharedContext.applicationBindings.containerPath
let additionalPaths: [String] = [
NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0],
containerPath + "/Documents/files",
containerPath + "/Documents/video",
containerPath + "/Documents/audio",
containerPath + "/Documents/mediacache",
containerPath + "/Documents/tempcache_v1/store",
]
return .single(nil)
|> then(collectCacheUsageStats(account: context.account, additionalCachePaths: additionalPaths, logFilesPath: context.sharedContext.applicationBindings.containerPath + "/telegram-data/logs")
|> map(Optional.init))
}
public func storageUsageController(context: AccountContext, cacheUsagePromise: Promise<CacheUsageStatsResult?>? = nil, isModal: Bool = false) -> ViewController {
let statePromise = ValuePromise(StoragUsageState(peerIdWithRevealedOptions: nil))
let stateValue = Atomic(value: StoragUsageState(peerIdWithRevealedOptions: nil))
let updateState: ((StoragUsageState) -> StoragUsageState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
let cacheSettingsPromise = Promise<CacheStorageSettings>()
cacheSettingsPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings])
|> map { sharedData -> CacheStorageSettings in
@ -289,59 +375,27 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
var presentControllerImpl: ((ViewController, PresentationContextType, Any?) -> Void)?
let statsPromise = Promise<CacheUsageStatsResult?>()
let resetStats: () -> Void = {
let containerPath = context.sharedContext.applicationBindings.containerPath
let additionalPaths: [String] = [
NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0],
containerPath + "/Documents/files",
containerPath + "/Documents/video",
containerPath + "/Documents/audio",
containerPath + "/Documents/mediacache",
containerPath + "/Documents/tempcache_v1/store",
]
statsPromise.set(.single(nil)
|> then(collectCacheUsageStats(account: context.account, additionalCachePaths: additionalPaths, logFilesPath: context.sharedContext.applicationBindings.containerPath + "/telegram-data/logs")
|> map(Optional.init)))
var statsPromise: Promise<CacheUsageStatsResult?>
if let cacheUsagePromise = cacheUsagePromise {
statsPromise = cacheUsagePromise
} else {
statsPromise = Promise<CacheUsageStatsResult?>()
statsPromise.set(cacheUsageStats(context: context))
}
let resetStats: () -> Void = {
statsPromise.set(cacheUsageStats(context: context))
}
resetStats()
let actionDisposables = DisposableSet()
let clearDisposable = MetaDisposable()
actionDisposables.add(clearDisposable)
let arguments = StorageUsageControllerArguments(account: context.account, updateKeepMedia: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationTheme: presentationData.theme)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
let timeoutAction: (Int32) -> Void = { timeout in
let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedDefaultCacheStorageTimeout(timeout)
}).start()
}
var values: [Int32] = [
3 * 24 * 60 * 60,
7 * 24 * 60 * 60,
1 * 31 * 24 * 60 * 60,
Int32.max
]
#if DEBUG
values.insert(60 * 60, at: 0)
#endif
let timeoutItems: [ActionSheetItem] = values.map { value in
return ActionSheetButtonItem(title: stringForKeepMediaTimeout(strings: presentationData.strings, timeout: value), action: {
dismissAction()
timeoutAction(value)
})
}
controller.setItemGroups([
ActionSheetItemGroup(items: timeoutItems),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
presentControllerImpl?(controller, .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
let arguments = StorageUsageControllerArguments(account: context.account, updateKeepMediaTimeout: { value in
let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedDefaultCacheStorageTimeout(value)
}).start()
}, openClearAll: {
let _ = (statsPromise.get()
|> take(1)
@ -535,8 +589,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
clearDisposable.set((signal
|> deliverOnMainQueue).start(completed: {
statsPromise.set(.single(.result(resultStats)))
let deviceName = UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone"
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", deviceName).0), elevatedLayout: false, action: { _ in }), .current, nil)
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in }), .current, nil)
}))
}
@ -717,8 +770,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
clearDisposable.set((signal
|> deliverOnMainQueue).start(completed: {
statsPromise.set(.single(.result(resultStats)))
let deviceName = UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone"
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", deviceName).0), elevatedLayout: false, action: { _ in }), .current, nil)
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in }), .current, nil)
}))
}
@ -734,18 +786,147 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
}
}
})
}, clearPeerMedia: { peerId in
let _ = (statsPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak statsPromise] result in
if let result = result, case let .result(stats) = result {
var additionalPeerId: PeerId?
if var categories = stats.media[peerId], let peer = stats.peers[peerId] {
if let channel = peer as? TelegramChannel, case .group = channel.info {
for (_, peer) in stats.peers {
if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference, migrationReference.peerId == peerId {
if let additionalCategories = stats.media[group.id] {
additionalPeerId = group.id
categories.merge(additionalCategories, uniquingKeysWith: { lhs, rhs in
return lhs.merging(rhs, uniquingKeysWith: { lhs, rhs in
return lhs + rhs
})
})
}
}
}
}
var sizeIndex: [PeerCacheUsageCategory: (Bool, Int64)] = [:]
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
}
}
if let statsPromise = statsPromise {
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
var clearMediaIds = Set<MediaId>()
var media = stats.media
if var categories = media[peerId] {
for category in clearCategories {
if let contents = categories[category] {
for (mediaId, _) in contents {
clearMediaIds.insert(mediaId)
}
}
categories.removeValue(forKey: category)
}
media[peerId] = categories
}
if let additionalPeerId = additionalPeerId {
if var categories = media[additionalPeerId] {
for category in clearCategories {
if let contents = categories[category] {
for (mediaId, _) in contents {
clearMediaIds.insert(mediaId)
}
}
categories.removeValue(forKey: category)
}
media[additionalPeerId] = categories
}
}
var clearResourceIds = Set<WrappedMediaResourceId>()
for id in clearMediaIds {
if let ids = stats.mediaResourceIds[id] {
for resourceId in ids {
clearResourceIds.insert(WrappedMediaResourceId(resourceId))
}
}
}
var signal = clearCachedMediaResources(account: context.account, mediaResourceIds: clearResourceIds)
let resultStats = 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)
var cancelImpl: (() -> Void)?
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let progressSignal = Signal<Never, NoError> { subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
cancelImpl?()
}))
presentControllerImpl?(controller, .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
signal = signal
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
cancelImpl = {
clearDisposable.set(nil)
resetStats()
}
clearDisposable.set((signal
|> deliverOnMainQueue).start(completed: {
statsPromise.set(.single(.result(resultStats)))
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in }), .current, nil)
}))
}
}
}
})
updateState { state in
return state.withUpdatedPeerIdWithRevealedOptions(nil)
}
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
return state.withUpdatedPeerIdWithRevealedOptions(peerId)
} else {
return state
}
}
})
var dismissImpl: (() -> Void)?
let signal = combineLatest(context.sharedContext.presentationData, cacheSettingsPromise.get(), statsPromise.get()) |> deliverOnMainQueue
|> map { presentationData, cacheSettings, cacheStats -> (ItemListControllerState, (ItemListNodeState, Any)) in
let signal = combineLatest(context.sharedContext.presentationData, cacheSettingsPromise.get(), statsPromise.get(), statePromise.get()) |> deliverOnMainQueue
|> map { presentationData, cacheSettings, cacheStats, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
let leftNavigationButton = isModal ? ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
}) : nil
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Cache_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(entries: storageUsageControllerEntries(presentationData: presentationData, cacheSettings: cacheSettings, cacheStats: cacheStats), style: .blocks, emptyStateItem: nil, animateChanges: false)
let listState = ItemListNodeState(entries: storageUsageControllerEntries(presentationData: presentationData, cacheSettings: cacheSettings, cacheStats: cacheStats, state: state), style: .blocks, emptyStateItem: nil, animateChanges: false)
return (controllerState, (listState, arguments))
} |> afterDisposed {

View File

@ -0,0 +1,318 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
import SyncCore
import TelegramUIPreferences
import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
struct StorageUsageCategory: Equatable {
let title: String
let size: Int64
let fraction: CGFloat
let color: UIColor
}
final class StorageUsageItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let dateTimeFormat: PresentationDateTimeFormat
let categories: [StorageUsageCategory]
let sectionId: ItemListSectionId
init(theme: PresentationTheme, dateTimeFormat: PresentationDateTimeFormat, categories: [StorageUsageCategory], sectionId: ItemListSectionId) {
self.theme = theme
self.dateTimeFormat = dateTimeFormat
self.categories = categories
self.sectionId = sectionId
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = StorageUsageItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? StorageUsageItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
}
private func generateDotImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 8.0, height: 8.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setFillColor(color.cgColor)
context.fillEllipse(in: bounds)
})
}
private func generateLineMaskImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 8.0, height: 8.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.setFillColor(color.cgColor)
context.fill(bounds)
context.setBlendMode(.clear)
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: bounds)
})?.stretchableImage(withLeftCapWidth: 4, topCapHeight: 4)
}
private final class StorageUsageItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
private let lineMaskNode: ASImageNode
private var lineNodes: [ASDisplayNode]
private var descriptionNodes: [(ASImageNode, TextNode)]
private var item: StorageUsageItem?
private var layoutParams: ListViewItemLayoutParams?
init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.maskNode = ASImageNode()
self.lineMaskNode = ASImageNode()
self.lineMaskNode.displaysAsynchronously = false
self.lineMaskNode.displayWithoutProcessing = true
self.lineMaskNode.contentMode = .scaleToFill
self.lineNodes = []
self.descriptionNodes = []
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.lineMaskNode)
}
func asyncLayout() -> (_ item: StorageUsageItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item
return { [weak self] item, params, neighbors in
if let strongSelf = self, strongSelf.lineNodes.count != item.categories.count {
for node in strongSelf.lineNodes {
node.removeFromSupernode()
}
strongSelf.lineNodes = []
for pair in strongSelf.descriptionNodes {
pair.0.removeFromSupernode()
pair.1.removeFromSupernode()
}
strongSelf.descriptionNodes = []
for _ in item.categories {
let lineNode = ASDisplayNode()
strongSelf.insertSubnode(lineNode, belowSubnode: strongSelf.lineMaskNode)
strongSelf.lineNodes.append(lineNode)
let dotNode = ASImageNode()
dotNode.displaysAsynchronously = false
dotNode.displayWithoutProcessing = true
strongSelf.addSubnode(dotNode)
let textNode = TextNode()
strongSelf.addSubnode(textNode)
strongSelf.descriptionNodes.append((dotNode, textNode))
}
}
var makeNodesLayout: [(TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode)] = []
if let strongSelf = self {
for nodes in strongSelf.descriptionNodes {
let makeTextLayout = TextNode.asyncLayout(nodes.1)
makeNodesLayout.append(makeTextLayout)
}
}
var themeUpdated = false
if currentItem?.theme !== item.theme {
themeUpdated = true
}
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
var textFramesApplies: [(CGRect, () -> TextNode)] = []
let inset: CGFloat = 16.0
let horizontalSpacing: CGFloat = 32.0
let verticalSpacing: CGFloat = 22.0
var textOrigin: CGPoint = CGPoint(x: horizontalSpacing, y: 52.0)
for i in 0 ..< item.categories.count {
let makeTextLayout = makeNodesLayout[i]
let category = item.categories[i]
let attributedString = NSMutableAttributedString(string: category.title, font: Font.regular(14.0), textColor: item.theme.list.itemPrimaryTextColor, paragraphAlignment: .natural)
attributedString.append(NSAttributedString(string: "\(dataSizeString(category.size, forceDecimal: true, decimalSeparator: item.dateTimeFormat.decimalSeparator))", font: Font.bold(14.0), textColor: item.theme.list.itemPrimaryTextColor))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 60.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var textFrame = CGRect(origin: textOrigin, size: textLayout.size)
if textFrame.maxX > params.width - params.rightInset - inset {
textFrame.origin = CGPoint(x: horizontalSpacing, y: textOrigin.y + verticalSpacing)
}
textOrigin = CGPoint(x: textFrame.maxX + horizontalSpacing, y: textFrame.minY)
textFramesApplies.append((textFrame, textApply))
}
contentSize = CGSize(width: params.width, height: textOrigin.y + 34.0)
insets = itemListNeighborsGroupedInsets(neighbors)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.item = item
strongSelf.layoutParams = params
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
if themeUpdated {
strongSelf.lineMaskNode.image = generateLineMaskImage(color: item.theme.list.itemBlocksBackgroundColor)
}
for (_, textApply) in textFramesApplies {
let _ = textApply()
}
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = 0.0
bottomStripeOffset = -separatorHeight
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
let lineInset: CGFloat = params.leftInset + 12.0
var lineOrigin = CGPoint(x: lineInset, y: 16.0)
let lineWidth = params.width - lineOrigin.x * 2.0
strongSelf.lineMaskNode.frame = CGRect(origin: lineOrigin, size: CGSize(width: lineWidth, height: 21.0))
for i in 0 ..< strongSelf.lineNodes.count {
let lineNode = strongSelf.lineNodes[i]
let category = item.categories[i]
lineNode.backgroundColor = category.color
var categoryWidth = max(floor(lineWidth * category.fraction), 2.0)
if i == strongSelf.lineNodes.count - 1 {
categoryWidth = lineWidth - (lineOrigin.x - lineInset)
}
let lineRect = CGRect(origin: lineOrigin, size: CGSize(width: categoryWidth, height: 21.0))
lineNode.frame = lineRect
lineOrigin.x += lineRect.width + 1.0
}
for i in 0 ..< strongSelf.descriptionNodes.count {
let dotNode = strongSelf.descriptionNodes[i].0
let textNode = strongSelf.descriptionNodes[i].1
let textFrame = textFramesApplies[i].0
let category = item.categories[i]
if dotNode.image == nil || themeUpdated {
dotNode.image = generateDotImage(color: category.color)
}
dotNode.frame = CGRect(x: textFrame.minX - 16.0, y: textFrame.minY + 4.0, width: 8.0, height: 8.0)
textNode.frame = textFrame
}
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
}

View File

@ -9,7 +9,7 @@ import ItemListUI
import PhotoResources
import OpenInExternalAppUI
public class WebBrowserItem: ListViewItem, ItemListItem {
class WebBrowserItem: ListViewItem, ItemListItem {
let account: Account
let theme: PresentationTheme
let title: String
@ -71,7 +71,7 @@ public class WebBrowserItem: ListViewItem, ItemListItem {
private let titleFont = Font.regular(17.0)
public class WebBrowserItemNode: ListViewItemNode {
private final class WebBrowserItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode

View File

@ -9,10 +9,13 @@ public func fetchHttpResource(url: String) -> Signal<MediaResourceDataFetchResul
return Signal { subscriber in
subscriber.putNext(.reset)
let disposable = signal.start(next: { next in
let data = next as! Data
let fetchResult: MediaResourceDataFetchResult = .dataPart(resourceOffset: 0, data: data, range: 0 ..< data.count, complete: true)
subscriber.putNext(fetchResult)
subscriber.putCompletion()
if let response = next as? MTHttpResponse {
let fetchResult: MediaResourceDataFetchResult = .dataPart(resourceOffset: 0, data: response.data, range: 0 ..< response.data.count, complete: true)
subscriber.putNext(fetchResult)
subscriber.putCompletion()
} else {
subscriber.putError(.generic)
}
}, error: { _ in
subscriber.putError(.generic)
}, completed: {

View File

@ -0,0 +1,12 @@
import UIKit
public func stringForDeviceType() -> String {
let model = UIDevice.current.model.lowercased()
if model.contains("ipad") {
return "iPad"
} else if model.contains("ipod") {
return "iPod touch"
} else {
return "iPhone"
}
}

View File

@ -3454,8 +3454,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self, strongSelf.beginMediaRecordingRequestId == requestId else {
return
}
guard checkAvailableDiskSpace(context: strongSelf.context, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
guard checkAvailableDiskSpace(context: strongSelf.context, push: { [weak self] c in
self?.push(c)
}) else {
return
}
@ -5292,8 +5292,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
disposable.set((signal
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self, let layout = strongSelf.validLayout {
let deviceName = UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone"
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", deviceName).0), elevatedLayout: true, action: { _ in }), in: .current)
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: true, action: { _ in }), in: .current)
}
}))
@ -5457,12 +5456,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}, openCamera: { [weak self] cameraView, menuController in
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
presentedLegacyCamera(context: strongSelf.context, peer: peer, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: !strongSelf.presentationInterfaceState.isScheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, sendMessagesWithSignals: { [weak self] signals in
presentedLegacyCamera(context: strongSelf.context, peer: peer, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: !strongSelf.presentationInterfaceState.isScheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
if let strongSelf = self {
if editMediaOptions != nil {
strongSelf.editMessageMediaWithLegacySignals(signals!)
} else {
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: false)
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime)
}
if !inputText.string.isEmpty {
//strongSelf.clearInputText()

View File

@ -103,7 +103,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
self.contentContainerNode.addSubnode(self.cancelButton)
self.contentContainerNode.addSubnode(self.doneButton)
if case .scheduledMessages(true) = self.mode {
self.contentContainerNode.addSubnode(self.onlineButton)
//self.contentContainerNode.addSubnode(self.onlineButton)
}
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
@ -316,7 +316,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
var buttonOffset: CGFloat = 0.0
if case .scheduledMessages(true) = self.mode {
buttonOffset += 60.0
//buttonOffset += 60.0
}
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom

View File

@ -7,7 +7,7 @@ import AlertUI
import PresentationDataUtils
import SettingsUI
func totalDiskSpace() -> Int64 {
private func totalDiskSpace() -> Int64 {
do {
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
return (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? 0
@ -16,7 +16,7 @@ func totalDiskSpace() -> Int64 {
}
}
func freeDiskSpace() -> Int64 {
private func freeDiskSpace() -> Int64 {
do {
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
return (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0
@ -25,18 +25,16 @@ func freeDiskSpace() -> Int64 {
}
}
func checkAvailableDiskSpace(context: AccountContext, threshold: Int64 = 100 * 1024 * 1024, present: @escaping (ViewController, Any?) -> Void) -> Bool {
func checkAvailableDiskSpace(context: AccountContext, threshold: Int64 = 100 * 1024 * 1024, push: @escaping (ViewController) -> Void) -> Bool {
guard freeDiskSpace() < threshold else {
return true
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = textAlertController(context: context, title: nil, text: presentationData.strings.Cache_LowDiskSpaceText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
let controller = storageUsageController(context: context, isModal: true)
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
push(storageUsageController(context: context, isModal: true))
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
present(controller, nil)
push(controller)
return false
}

View File

@ -11,7 +11,7 @@ import ShareController
import LegacyUI
import LegacyMediaPickerUI
func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, hasSchedule: Bool, sendMessagesWithSignals: @escaping ([Any]?) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void) {
func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, hasSchedule: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
@ -29,6 +29,12 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
controller = TGCameraController()
}
controller.presentScheduleController = { done in
presentSchedulePicker { time in
done?(time)
}
}
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
} else {
controller.customPresentOverlayController = { [weak legacyController] generateController in
@ -99,10 +105,10 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
legacyController?.dismiss()
}
controller.finishedWithResults = { [weak menuController, weak legacyController] overlayController, selectionContext, editingContext, currentItem in
controller.finishedWithResults = { [weak menuController, weak legacyController] overlayController, selectionContext, editingContext, currentItem, silentPosting, scheduleTime in
if let selectionContext = selectionContext, let editingContext = editingContext {
let signals = TGCameraController.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, storeAssets: saveCapturedPhotos && !isSecretChat, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, descriptionGenerator: legacyAssetPickerItemGenerator())
sendMessagesWithSignals(signals)
sendMessagesWithSignals(signals, silentPosting, scheduleTime)
}
menuController?.dismiss(animated: false)
@ -118,7 +124,7 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
description["timer"] = timer
}
if let item = legacyAssetPickerItemGenerator()(description, caption, entities, nil) {
sendMessagesWithSignals([SSignal.single(item)])
sendMessagesWithSignals([SSignal.single(item)], false, 0)
}
}
@ -143,7 +149,7 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
description["timer"] = timer
}
if let item = legacyAssetPickerItemGenerator()(description, caption, entities, nil) {
sendMessagesWithSignals([SSignal.single(item)])
sendMessagesWithSignals([SSignal.single(item)], false, 0)
}
}
menuController?.dismiss(animated: false)
@ -193,7 +199,7 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B
legacyController?.dismiss()
}
controller.finishedWithResults = { [weak controller, weak parentController, weak legacyController] overlayController, selectionContext, editingContext, currentItem in
controller.finishedWithResults = { [weak controller, weak parentController, weak legacyController] overlayController, selectionContext, editingContext, currentItem, _, _ in
if let selectionContext = selectionContext, let editingContext = editingContext {
let signals = TGCameraController.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, storeAssets: saveCapturedMedia, saveEditedPhotos: saveEditedPhotos, descriptionGenerator: legacyAssetPickerItemGenerator())
if let parentController = parentController {
@ -218,77 +224,7 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B
}), showInChat: nil, externalShare: false), in: .window(.root))
}
}
//legacyController?.dismissWithAnimation()
}
parentController.present(legacyController, in: .window(.root))
/*TGCameraControllerWindow *controllerWindow = [[TGCameraControllerWindow alloc] initWithManager:[[TGLegacyComponentsContext shared] makeOverlayWindowManager] parentController:TGAppDelegateInstance.rootController contentController:controller];
controllerWindow.hidden = false;
CGSize screenSize = TGScreenSize();
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
controllerWindow.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
CGRect startFrame = CGRectMake(0, screenSize.height, screenSize.width, screenSize.height);
[controller beginTransitionInFromRect:startFrame];
__weak TGCameraController *weakCameraController = controller;
controller.finishedWithResults = ^(TGOverlayController *controller, TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem)
{
__autoreleasing NSString *disabledMessage = nil;
if (![TGApplicationFeatures isPhotoUploadEnabledForPeerType:TGApplicationFeaturePeerPrivate disabledMessage:&disabledMessage])
{
[TGCustomAlertView presentAlertWithTitle:TGLocalized(@"FeatureDisabled.Oops") message:disabledMessage cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil];
return;
}
__strong TGCameraController *strongCameraController = weakCameraController;
if (strongCameraController == nil)
return;
[TGCameraController showTargetController:[TGCameraController resultSignalsForSelectionContext:selectionContext editingContext:editingContext currentItem:currentItem storeAssets:false saveEditedPhotos:false descriptionGenerator:^id(id item, NSString *caption, NSArray *entities, __unused NSString *stickers)
{
if ([item isKindOfClass:[NSDictionary class]])
{
NSDictionary *dict = (NSDictionary *)item;
NSString *type = dict[@"type"];
if ([type isEqualToString:@"editedPhoto"])
{
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
result[@"type"] = @"image";
result[@"image"] = dict[@"image"];
if (caption.length > 0)
result[@"caption"] = caption;
if (entities.count > 0)
result[@"entities"] = entities;
if (dict[@"stickers"] != nil)
result[@"stickers"] = dict[@"stickers"];
return result;
}
else if ([type isEqualToString:@"cameraVideo"])
{
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
result[@"type"] = @"cameraVideo";
result[@"url"] = dict[@"url"];
if (dict[@"adjustments"] != nil)
result[@"adjustments"] = dict[@"adjustments"];
if (entities.count > 0)
result[@"entities"] = entities;
if (dict[@"stickers"] != nil)
result[@"stickers"] = dict[@"stickers"];
if (dict[@"previewImage"] != nil)
result[@"previewImage"] = dict[@"previewImage"];
return result;
}
}
return nil;
}] cameraController:strongCameraController resultController:controller navigationController:(TGNavigationController *)controller.navigationController];
};*/
}

View File

@ -79,6 +79,8 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
private var didPlayToEndTimeObserver: NSObjectProtocol?
private var timeObserver: Any?
private var seekId: Int = 0
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, url: String, imageReference: ImageMediaReference, intrinsicDimensions: CGSize, approximateDuration: Int32) {
self.audioSessionManager = audioSessionManager
@ -131,7 +133,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
guard let strongSelf = self else {
return
}
strongSelf.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: strongSelf.statusValue.duration, dimensions: CGSize(), timestamp: CMTimeGetSeconds(time), baseRate: 1.0, seekId: 0, status: strongSelf.statusValue.status, soundEnabled: true)
strongSelf.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: strongSelf.statusValue.duration, dimensions: CGSize(), timestamp: CMTimeGetSeconds(time), baseRate: 1.0, seekId: strongSelf.seekId, status: strongSelf.statusValue.status, soundEnabled: true)
strongSelf._status.set(strongSelf.statusValue)
}
}
@ -171,7 +173,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
} else {
status = isPlaying ? .playing : .paused
}
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: 0, status: status, soundEnabled: true)
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: self.seekId, status: status, soundEnabled: true)
self._status.set(self.statusValue)
} else if keyPath == "playbackBufferEmpty" {
let isPlaying = !self.player.rate.isZero
@ -182,7 +184,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
} else {
status = isPlaying ? .playing : .paused
}
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: 0, status: status, soundEnabled: true)
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: self.seekId, status: status, soundEnabled: true)
self._status.set(self.statusValue)
} else if keyPath == "playbackLikelyToKeepUp" || keyPath == "playbackBufferFull" {
let isPlaying = !self.player.rate.isZero
@ -193,7 +195,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
} else {
status = isPlaying ? .playing : .paused
}
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: 0, status: status, soundEnabled: true)
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: self.seekId, status: status, soundEnabled: true)
self._status.set(self.statusValue)
if !self.didBeginPlaying {
@ -217,7 +219,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
func play() {
assert(Queue.mainQueue().isCurrent())
if !self.initializedStatus {
self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .buffering(initial: true, whilePlaying: true), soundEnabled: true))
self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true), soundEnabled: true))
}
if !self.hasAudioSession {
self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play, activate: { [weak self] _ in
@ -236,7 +238,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
func pause() {
assert(Queue.mainQueue().isCurrent())
if !self.initializedStatus {
self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true))
self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: self.seekId, status: .paused, soundEnabled: true))
}
self.player.pause()
}
@ -256,6 +258,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
func seek(_ timestamp: Double) {
assert(Queue.mainQueue().isCurrent())
self.seekId += 1
self.playerItem.seek(to: CMTimeMake(value: Int64(timestamp) * 1000, timescale: 1000))
}

View File

@ -187,270 +187,273 @@ public final class WalletStrings: Equatable {
private let _s: [Int: String]
private let _r: [Int: [(Int, NSRange)]]
private let _ps: [Int: String]
public var Wallet_Updated_JustNow: String { return self._s[0]! }
public var Wallet_WordCheck_IncorrectText: String { return self._s[1]! }
public var Wallet_Month_ShortNovember: String { return self._s[2]! }
public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[3]! }
public var Wallet_Info_Send: String { return self._s[4]! }
public var Wallet_TransactionInfo_SendGrams: String { return self._s[5]! }
public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[6]!, self._r[6]!, [_0])
public var Wallet_Created_ExportErrorText: String { return self._s[0]! }
public var Wallet_Send_ConfirmationConfirm: String { return self._s[2]! }
public var Wallet_Month_GenJuly: String { return self._s[3]! }
public var Wallet_Month_GenDecember: String { return self._s[4]! }
public var Wallet_Month_ShortJanuary: String { return self._s[5]! }
public var Wallet_WordCheck_Title: String { return self._s[6]! }
public var Wallet_Month_ShortMarch: String { return self._s[7]! }
public var Wallet_Month_GenSeptember: String { return self._s[8]! }
public var Wallet_Info_Address: String { return self._s[9]! }
public var Wallet_RestoreFailed_CreateWallet: String { return self._s[10]! }
public var Wallet_Intro_NotNow: String { return self._s[11]! }
public var Wallet_AccessDenied_Camera: String { return self._s[12]! }
public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[13]! }
public var Wallet_Receive_ShareAddress: String { return self._s[14]! }
public var Wallet_Sent_ViewWallet: String { return self._s[15]! }
public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[16]! }
public var Wallet_Completed_Text: String { return self._s[17]! }
public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[18]!, self._r[18]!, [_0])
}
public var Wallet_Sent_Title: String { return self._s[7]! }
public var Wallet_Receive_ShareUrlInfo: String { return self._s[8]! }
public var Wallet_RestoreFailed_Title: String { return self._s[9]! }
public var Wallet_TransactionInfo_CopyAddress: String { return self._s[11]! }
public var Wallet_Settings_BackupWallet: String { return self._s[12]! }
public var Wallet_Send_NetworkErrorTitle: String { return self._s[13]! }
public var Wallet_Month_ShortJune: String { return self._s[14]! }
public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[15]! }
public var Wallet_Created_Title: String { return self._s[16]! }
public func Wallet_Configuration_ApplyErrorTextURLUnreachable(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[17]!, self._r[17]!, [_0])
public var Wallet_Receive_AmountText: String { return self._s[19]! }
public var Wallet_TransactionInfo_CommentHeader: String { return self._s[20]! }
public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[21]!, self._r[21]!, [_0])
}
public var Wallet_Send_SyncInProgress: String { return self._s[18]! }
public var Wallet_Info_YourBalance: String { return self._s[19]! }
public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[20]! }
public var Wallet_TransactionInfo_CommentHeader: String { return self._s[21]! }
public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[22]! }
public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[23]!, self._r[23]!, [_1, _2, _3])
}
public var Wallet_Settings_ConfigurationInfo: String { return self._s[24]! }
public var Wallet_WordImport_IncorrectText: String { return self._s[25]! }
public var Wallet_Month_GenJanuary: String { return self._s[26]! }
public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[27]! }
public var Wallet_Receive_ShareAddress: String { return self._s[28]! }
public var Wallet_WordImport_Title: String { return self._s[29]! }
public var Wallet_TransactionInfo_Title: String { return self._s[30]! }
public var Wallet_Words_NotDoneText: String { return self._s[32]! }
public var Wallet_RestoreFailed_EnterWords: String { return self._s[33]! }
public var Wallet_WordImport_Text: String { return self._s[34]! }
public var Wallet_RestoreFailed_Text: String { return self._s[36]! }
public var Wallet_TransactionInfo_NoAddress: String { return self._s[37]! }
public var Wallet_Navigation_Back: String { return self._s[38]! }
public var Wallet_Intro_Terms: String { return self._s[39]! }
public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[22]! }
public var Wallet_SecureStorageReset_Title: String { return self._s[23]! }
public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[24]! }
public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[25]! }
public var Wallet_Month_GenApril: String { return self._s[26]! }
public var Wallet_AccessDenied_Settings: String { return self._s[27]! }
public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[28]! }
public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[29]! }
public var Wallet_Qr_Title: String { return self._s[30]! }
public var Wallet_Intro_ImportExisting: String { return self._s[31]! }
public var Wallet_Send_OwnAddressAlertText: String { return self._s[32]! }
public var Wallet_Month_GenAugust: String { return self._s[33]! }
public var Wallet_Month_ShortDecember: String { return self._s[34]! }
public var Wallet_Info_Receive: String { return self._s[35]! }
public var Wallet_Send_Send: String { return self._s[36]! }
public var Wallet_RestoreFailed_Text: String { return self._s[37]! }
public var Wallet_Navigation_Cancel: String { return self._s[38]! }
public var Wallet_CreateInvoice_Title: String { return self._s[39]! }
public var Wallet_Sent_Title: String { return self._s[40]! }
public var Wallet_WordCheck_Continue: String { return self._s[41]! }
public var Wallet_Send_SyncInProgress: String { return self._s[43]! }
public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[40]!, self._r[40]!, [_0])
return formatWithArgumentRanges(self._s[44]!, self._r[44]!, [_0])
}
public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[41]!, self._r[41]!, [_1, _2, _3])
}
public var Wallet_TransactionInfo_AddressCopied: String { return self._s[42]! }
public var Wallet_Month_GenMay: String { return self._s[45]! }
public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[46]! }
public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[47]! }
public var Wallet_Receive_AddressHeader: String { return self._s[48]! }
public var Wallet_Settings_BackupWallet: String { return self._s[50]! }
public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[51]! }
public var Wallet_Info_Send: String { return self._s[52]! }
public var Wallet_Intro_Title: String { return self._s[53]! }
public var Wallet_Receive_Title: String { return self._s[54]! }
public var Wallet_Configuration_SourceHeader: String { return self._s[55]! }
public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[56]! }
public var Wallet_Alert_OK: String { return self._s[57]! }
public var Wallet_Send_NetworkErrorText: String { return self._s[58]! }
public var Wallet_Receive_CommentInfo: String { return self._s[59]! }
public var Wallet_TransactionInfo_Title: String { return self._s[60]! }
public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[61]! }
public var Wallet_Completed_Title: String { return self._s[62]! }
public var Wallet_Info_YourBalance: String { return self._s[63]! }
public var Wallet_Configuration_Title: String { return self._s[64]! }
public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[43]!, self._r[43]!, [_1, _2, _3])
return formatWithArgumentRanges(self._s[65]!, self._r[65]!, [_1, _2, _3])
}
public var Wallet_Send_NetworkErrorText: String { return self._s[44]! }
public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[45]! }
public var Wallet_Intro_ImportExisting: String { return self._s[46]! }
public var Wallet_Receive_CommentInfo: String { return self._s[47]! }
public var Wallet_WordCheck_Continue: String { return self._s[48]! }
public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[49]! }
public var Wallet_Completed_Text: String { return self._s[50]! }
public var Wallet_WordCheck_IncorrectHeader: String { return self._s[52]! }
public var Wallet_Configuration_SourceHeader: String { return self._s[53]! }
public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[54]! }
public var Wallet_Receive_Title: String { return self._s[55]! }
public var Wallet_Info_WalletCreated: String { return self._s[56]! }
public var Wallet_Navigation_Cancel: String { return self._s[57]! }
public var Wallet_CreateInvoice_Title: String { return self._s[58]! }
public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[59]!, self._r[59]!, [_1, _2, _3])
}
public var Wallet_TransactionInfo_SenderHeader: String { return self._s[60]! }
public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[61]!, self._r[61]!, [_1, _2, _3])
}
public var Wallet_Month_GenAugust: String { return self._s[62]! }
public var Wallet_Info_UnknownTransaction: String { return self._s[63]! }
public var Wallet_Receive_CreateInvoice: String { return self._s[64]! }
public var Wallet_Month_GenSeptember: String { return self._s[65]! }
public var Wallet_Month_GenJuly: String { return self._s[66]! }
public var Wallet_Receive_AddressHeader: String { return self._s[67]! }
public var Wallet_Send_AmountText: String { return self._s[68]! }
public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[69]! }
public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[70]!, self._r[70]!, [_1, _2, _3])
}
public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[71]!, self._r[71]!, [_0])
}
public var Wallet_Configuration_Title: String { return self._s[73]! }
public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[74]! }
public var Wallet_Words_Title: String { return self._s[75]! }
public var Wallet_Month_ShortMay: String { return self._s[76]! }
public var Wallet_WordCheck_Title: String { return self._s[77]! }
public var Wallet_Words_NotDoneResponse: String { return self._s[78]! }
public var Wallet_Configuration_SourceURL: String { return self._s[79]! }
public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[80]! }
public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[81]! }
public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[82]!, self._r[82]!, [_1, _2, _3])
}
public var Wallet_Info_Address: String { return self._s[83]! }
public var Wallet_Intro_CreateWallet: String { return self._s[84]! }
public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[85]! }
public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[66]!, self._r[66]!, [_0])
}
public var Wallet_Month_ShortJune: String { return self._s[67]! }
public var Wallet_ContextMenuCopy: String { return self._s[68]! }
public var Wallet_WordCheck_ViewWords: String { return self._s[69]! }
public var Wallet_Send_Title: String { return self._s[70]! }
public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[71]! }
public var Wallet_WordImport_IncorrectText: String { return self._s[72]! }
public var Wallet_Weekday_Yesterday: String { return self._s[73]! }
public var Wallet_Send_AddressInfo: String { return self._s[74]! }
public var Wallet_UnknownError: String { return self._s[75]! }
public var Wallet_Receive_CopyAddress: String { return self._s[76]! }
public var Wallet_Month_ShortFebruary: String { return self._s[77]! }
public var Wallet_Intro_CreateWallet: String { return self._s[78]! }
public var Wallet_Created_ExportErrorTitle: String { return self._s[80]! }
public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[81]! }
public var Wallet_Info_TransactionFrom: String { return self._s[82]! }
public var Wallet_Month_ShortNovember: String { return self._s[83]! }
public var Wallet_Month_ShortSeptember: String { return self._s[84]! }
public func Wallet_Configuration_ApplyErrorTextURLUnreachable(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[85]!, self._r[85]!, [_0])
}
public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[86]!, self._r[86]!, [_0])
}
public var Wallet_Send_SendAnyway: String { return self._s[87]! }
public var Wallet_UnknownError: String { return self._s[88]! }
public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[89]! }
public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[90]! }
public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[92]! }
public var Wallet_Configuration_SourceInfo: String { return self._s[93]! }
public var Wallet_Words_NotDoneOk: String { return self._s[94]! }
public var Wallet_Intro_Title: String { return self._s[95]! }
public var Wallet_Info_Receive: String { return self._s[96]! }
public var Wallet_Completed_ViewWallet: String { return self._s[97]! }
public var Wallet_Month_ShortJuly: String { return self._s[98]! }
public var Wallet_Month_ShortApril: String { return self._s[99]! }
public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[100]!, self._r[100]!, [_1, _2])
public var Wallet_Send_AmountText: String { return self._s[87]! }
public var Wallet_Info_TransactionTo: String { return self._s[88]! }
public var Wallet_Words_Done: String { return self._s[89]! }
public var Wallet_Created_Text: String { return self._s[90]! }
public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[91]! }
public var Wallet_Month_ShortJuly: String { return self._s[92]! }
public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[93]!, self._r[93]!, [_0])
}
public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[101]! }
public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
public var Wallet_Created_Title: String { return self._s[94]! }
public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[95]!, self._r[95]!, [_1, _2, _3])
}
public var Wallet_Info_RefreshErrorTitle: String { return self._s[96]! }
public var Wallet_Info_ReceiveGrams: String { return self._s[97]! }
public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[98]! }
public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[99]! }
public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[100]! }
public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[101]! }
public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[102]!, self._r[102]!, [_1, _2, _3])
}
public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[103]! }
public var Wallet_Send_UninitializedText: String { return self._s[104]! }
public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[105]!, self._r[105]!, [_0])
}
public var Wallet_Month_GenNovember: String { return self._s[106]! }
public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[107]!, self._r[107]!, [_1, _2, _3])
}
public var Wallet_Month_GenApril: String { return self._s[108]! }
public var Wallet_Month_ShortMarch: String { return self._s[109]! }
public var Wallet_Month_GenFebruary: String { return self._s[110]! }
public var Wallet_Qr_ScanCode: String { return self._s[111]! }
public var Wallet_Receive_AddressCopied: String { return self._s[112]! }
public var Wallet_Send_UninitializedTitle: String { return self._s[113]! }
public var Wallet_Send_Send: String { return self._s[114]! }
public var Wallet_Info_RefreshErrorTitle: String { return self._s[115]! }
public var Wallet_Month_GenJune: String { return self._s[116]! }
public var Wallet_Send_AddressHeader: String { return self._s[117]! }
public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[118]! }
public var Wallet_Send_Confirmation: String { return self._s[119]! }
public var Wallet_Completed_Title: String { return self._s[120]! }
public var Wallet_Alert_OK: String { return self._s[121]! }
public var Wallet_Settings_DeleteWallet: String { return self._s[122]! }
public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[123]! }
public var Wallet_Month_ShortSeptember: String { return self._s[124]! }
public var Wallet_Info_TransactionTo: String { return self._s[125]! }
public var Wallet_Send_ConfirmationConfirm: String { return self._s[126]! }
public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[127]! }
public var Wallet_Receive_AmountText: String { return self._s[128]! }
public var Wallet_Receive_CopyAddress: String { return self._s[129]! }
public var Wallet_Intro_Text: String { return self._s[131]! }
public var Wallet_Configuration_Apply: String { return self._s[132]! }
public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[133]!, self._r[133]!, [_0])
}
public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[134]!, self._r[134]!, [_1, _2, _3])
}
public var Wallet_RestoreFailed_CreateWallet: String { return self._s[135]! }
public var Wallet_Weekday_Yesterday: String { return self._s[136]! }
public var Wallet_Receive_AmountHeader: String { return self._s[137]! }
public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[138]! }
public var Wallet_Month_ShortFebruary: String { return self._s[139]! }
public var Wallet_Configuration_SourceJSON: String { return self._s[140]! }
public var Wallet_Alert_Cancel: String { return self._s[141]! }
public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[142]! }
public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[143]! }
public var Wallet_Info_TransactionFrom: String { return self._s[144]! }
public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[145]! }
public var Wallet_Send_OwnAddressAlertText: String { return self._s[146]! }
public var Wallet_Words_NotDoneTitle: String { return self._s[147]! }
public var Wallet_Month_ShortOctober: String { return self._s[148]! }
public var Wallet_Month_GenMay: String { return self._s[149]! }
public var Wallet_Intro_CreateErrorTitle: String { return self._s[150]! }
public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[151]! }
public var Wallet_Month_ShortJanuary: String { return self._s[152]! }
public var Wallet_Month_GenMarch: String { return self._s[153]! }
public var Wallet_Sending_Text: String { return self._s[154]! }
public var Wallet_Month_GenOctober: String { return self._s[155]! }
public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[156]! }
public var Wallet_ContextMenuCopy: String { return self._s[157]! }
public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[158]!, self._r[158]!, [_1, _2, _3])
}
public var Wallet_Info_Updating: String { return self._s[160]! }
public var Wallet_Created_ExportErrorTitle: String { return self._s[161]! }
public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[162]! }
public var Wallet_Sending_Title: String { return self._s[163]! }
public var Wallet_Navigation_Done: String { return self._s[164]! }
public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[165]! }
public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[166]! }
public var Wallet_Settings_Title: String { return self._s[167]! }
public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[168]!, self._r[168]!, [_0])
}
public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[169]! }
public var Wallet_Weekday_Today: String { return self._s[171]! }
public var Wallet_Month_ShortDecember: String { return self._s[172]! }
public var Wallet_Words_Text: String { return self._s[173]! }
public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[174]! }
public var Wallet_WordCheck_ViewWords: String { return self._s[175]! }
public var Wallet_Send_AddressInfo: String { return self._s[176]! }
public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[177]!, self._r[177]!, [_0])
}
public var Wallet_Intro_NotNow: String { return self._s[178]! }
public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[179]! }
public var Wallet_Navigation_Close: String { return self._s[180]! }
public var Wallet_Month_GenDecember: String { return self._s[182]! }
public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[183]! }
public var Wallet_WordImport_IncorrectTitle: String { return self._s[184]! }
public var Wallet_Send_AddressText: String { return self._s[185]! }
public var Wallet_Receive_AmountInfo: String { return self._s[186]! }
public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[187]!, self._r[187]!, [_1, _2, _3])
}
public var Wallet_Month_ShortAugust: String { return self._s[188]! }
public var Wallet_Qr_Title: String { return self._s[189]! }
public var Wallet_Settings_Configuration: String { return self._s[190]! }
public var Wallet_WordCheck_TryAgain: String { return self._s[191]! }
public var Wallet_Info_TransactionPendingHeader: String { return self._s[192]! }
public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[193]! }
public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[194]! }
public var Wallet_Send_TransactionInProgress: String { return self._s[195]! }
public var Wallet_Created_Text: String { return self._s[196]! }
public var Wallet_Created_Proceed: String { return self._s[197]! }
public var Wallet_Words_Done: String { return self._s[198]! }
public var Wallet_WordImport_Continue: String { return self._s[199]! }
public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[200]! }
public var Wallet_WordImport_CanNotRemember: String { return self._s[201]! }
public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[202]!, self._r[202]!, [_1, _2, _3])
}
public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[203]!, self._r[203]!, [_1, _2, _3])
}
public var Wallet_Created_ExportErrorText: String { return self._s[205]! }
public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[206]!, self._r[206]!, [_0])
}
public var Wallet_Settings_DeleteWalletInfo: String { return self._s[207]! }
public var Wallet_Intro_CreateErrorText: String { return self._s[208]! }
public var Wallet_Sent_ViewWallet: String { return self._s[209]! }
public var Wallet_Send_ErrorInvalidAddress: String { return self._s[210]! }
public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[211]! }
public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[105]! }
public var Wallet_Send_ErrorInvalidAddress: String { return self._s[106]! }
public var Wallet_Words_NotDoneTitle: String { return self._s[107]! }
public var Wallet_Navigation_Back: String { return self._s[108]! }
public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[212]!, self._r[212]!, [_1, _2, _3])
return formatWithArgumentRanges(self._s[109]!, self._r[109]!, [_1, _2, _3])
}
public var Wallet_Send_Title: String { return self._s[213]! }
public var Wallet_Info_RefreshErrorText: String { return self._s[214]! }
public var Wallet_SecureStorageReset_Title: String { return self._s[215]! }
public var Wallet_Receive_CommentHeader: String { return self._s[216]! }
public var Wallet_Info_ReceiveGrams: String { return self._s[217]! }
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
public var Wallet_Info_UnknownTransaction: String { return self._s[110]! }
public var Wallet_RestoreFailed_Title: String { return self._s[111]! }
public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[112]!, self._r[112]!, [_0])
}
public var Wallet_TransactionInfo_CopyAddress: String { return self._s[113]! }
public var Wallet_Navigation_Done: String { return self._s[114]! }
public var Wallet_Send_UninitializedTitle: String { return self._s[115]! }
public var Wallet_Send_AddressHeader: String { return self._s[117]! }
public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[118]!, self._r[118]!, [_1, _2, _3])
}
public var Wallet_Alert_Cancel: String { return self._s[119]! }
public var Wallet_Send_NetworkErrorTitle: String { return self._s[120]! }
public var Wallet_Configuration_SourceInfo: String { return self._s[121]! }
public var Wallet_Month_ShortAugust: String { return self._s[122]! }
public var Wallet_Words_NotDoneResponse: String { return self._s[123]! }
public var Wallet_WordCheck_TryAgain: String { return self._s[124]! }
public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[125]!, self._r[125]!, [_1, _2, _3])
}
public var Wallet_Words_Text: String { return self._s[126]! }
public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[127]!, self._r[127]!, [_1, _2, _3])
}
public var Wallet_Month_ShortOctober: String { return self._s[128]! }
public var Wallet_Created_Proceed: String { return self._s[129]! }
public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[130]!, self._r[130]!, [_0])
}
public var Wallet_Month_ShortApril: String { return self._s[131]! }
public var Wallet_Navigation_Close: String { return self._s[132]! }
public var Wallet_WordCheck_IncorrectHeader: String { return self._s[133]! }
public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[134]! }
public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[135]!, self._r[135]!, [_1, _2, _3])
}
public var Wallet_Send_AddressText: String { return self._s[137]! }
public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[139]!, self._r[139]!, [_1, _2, _3])
}
public var Wallet_Month_ShortMay: String { return self._s[140]! }
public var Wallet_Intro_CreateErrorText: String { return self._s[142]! }
public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[143]! }
public var Wallet_Intro_Text: String { return self._s[144]! }
public var Wallet_Month_GenJune: String { return self._s[145]! }
public var Wallet_Receive_ShareUrlInfo: String { return self._s[146]! }
public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[147]!, self._r[147]!, [_1, _2, _3])
}
public var Wallet_AccessDenied_Title: String { return self._s[149]! }
public var Wallet_Send_SendAnyway: String { return self._s[150]! }
public var Wallet_Configuration_SourceJSON: String { return self._s[151]! }
public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[152]! }
public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[153]!, self._r[153]!, [_1, _2, _3])
}
public var Wallet_TransactionInfo_SendGrams: String { return self._s[154]! }
public var Wallet_Words_NotDoneText: String { return self._s[155]! }
public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[156]! }
public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[158]! }
public var Wallet_Qr_ScanCode: String { return self._s[159]! }
public var Wallet_WordImport_Title: String { return self._s[160]! }
public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[161]!, self._r[161]!, [_1, _2, _3])
}
public var Wallet_Intro_Terms: String { return self._s[162]! }
public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[163]! }
public var Wallet_Receive_CreateInvoice: String { return self._s[164]! }
public var Wallet_Send_Confirmation: String { return self._s[165]! }
public var Wallet_Month_GenNovember: String { return self._s[166]! }
public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[167]!, self._r[167]!, [_1, _2])
}
public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[168]!, self._r[168]!, [_1, _2, _3])
}
public var Wallet_WordCheck_IncorrectText: String { return self._s[170]! }
public var Wallet_Completed_ViewWallet: String { return self._s[171]! }
public var Wallet_WordImport_Text: String { return self._s[172]! }
public var Wallet_Words_NotDoneOk: String { return self._s[173]! }
public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[174]! }
public var Wallet_TransactionInfo_SenderHeader: String { return self._s[175]! }
public var Wallet_Info_WalletCreated: String { return self._s[176]! }
public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[177]! }
public var Wallet_Sending_Text: String { return self._s[178]! }
public var Wallet_Words_Title: String { return self._s[179]! }
public var Wallet_Receive_CommentHeader: String { return self._s[180]! }
public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[181]! }
public var Wallet_Intro_CreateErrorTitle: String { return self._s[182]! }
public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[183]!, self._r[183]!, [_1, _2, _3])
}
public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[184]! }
public var Wallet_Configuration_Apply: String { return self._s[185]! }
public var Wallet_WordImport_IncorrectTitle: String { return self._s[186]! }
public var Wallet_Settings_Configuration: String { return self._s[187]! }
public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[189]! }
public var Wallet_Month_GenJanuary: String { return self._s[190]! }
public var Wallet_Settings_DeleteWallet: String { return self._s[191]! }
public var Wallet_Month_GenOctober: String { return self._s[192]! }
public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[193]! }
public var Wallet_Receive_AddressCopied: String { return self._s[194]! }
public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[195]! }
public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[196]!, self._r[196]!, [_1, _2, _3])
}
public var Wallet_Receive_AmountInfo: String { return self._s[197]! }
public var Wallet_Info_TransactionPendingHeader: String { return self._s[198]! }
public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[199]! }
public var Wallet_Updated_JustNow: String { return self._s[200]! }
public var Wallet_Info_RefreshErrorText: String { return self._s[201]! }
public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[202]! }
public var Wallet_Settings_DeleteWalletInfo: String { return self._s[203]! }
public var Wallet_RestoreFailed_EnterWords: String { return self._s[204]! }
public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[205]! }
public var Wallet_Send_TransactionInProgress: String { return self._s[206]! }
public var Wallet_Month_GenMarch: String { return self._s[207]! }
public var Wallet_Receive_AmountHeader: String { return self._s[208]! }
public var Wallet_TransactionInfo_NoAddress: String { return self._s[209]! }
public var Wallet_Weekday_Today: String { return self._s[210]! }
public var Wallet_Configuration_SourceURL: String { return self._s[211]! }
public var Wallet_TransactionInfo_AddressCopied: String { return self._s[212]! }
public var Wallet_WordImport_Continue: String { return self._s[213]! }
public var Wallet_Month_GenFebruary: String { return self._s[214]! }
public var Wallet_Settings_Title: String { return self._s[215]! }
public var Wallet_Info_Updating: String { return self._s[216]! }
public var Wallet_Settings_ConfigurationInfo: String { return self._s[217]! }
public var Wallet_WordImport_CanNotRemember: String { return self._s[218]! }
public var Wallet_Sending_Title: String { return self._s[219]! }
public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) {
return formatWithArgumentRanges(self._s[220]!, self._r[220]!, [_0])
}
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
}
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)

View File

@ -2731,14 +2731,12 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
guard let strongSelf = self else {
return
}
if node.isLast {
if done {
action()
if let index = strongSelf.inputNodes.firstIndex(where: { $0 === node }) {
if index == strongSelf.inputNodes.count - 1 {
if done {
action()
}
} else {
strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -20.0), animated: true)
}
} else {
if let index = strongSelf.inputNodes.firstIndex(where: { $0 === node }), index != strongSelf.inputNodes.count - 1 {
strongSelf.inputNodes[index + 1].focus()
}
}
@ -2749,7 +2747,9 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
return
}
if node.isLast {
strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -10.0), animated: true)
UIView.animate(withDuration: 0.3, animations: {
strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -20.0), animated: false)
})
} else {
strongSelf.scrollNode.view.scrollRectToVisible(node.frame.insetBy(dx: 0.0, dy: -10.0), animated: true)
}