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."; "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_1" = "Deleted 1 chat";
"ChatList.DeletedChats_any" = "Deleted %@ chats"; "ChatList.DeletedChats_any" = "Deleted %@ chats";

View File

@ -402,10 +402,19 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
} }
func authorizeAccessToCamera(completion: @escaping () -> Void) { 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 { Queue.mainQueue().async {
if response { if response {
completion() 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( let presentationData = WalletPresentationData(
theme: WalletTheme( theme: WalletTheme(
info: WalletInfoTheme( info: WalletInfoTheme(
buttonBackgroundColor: accentColor, buttonBackgroundColor: UIColor(rgb: 0x32aafe),
buttonTextColor: .white, buttonTextColor: .white,
incomingFundsTitleColor: UIColor(rgb: 0x00b12c), incomingFundsTitleColor: UIColor(rgb: 0x00b12c),
outgoingFundsTitleColor: UIColor(rgb: 0xff3b30) outgoingFundsTitleColor: UIColor(rgb: 0xff3b30)

View File

@ -218,3 +218,6 @@
"Wallet.Time.PreciseDate_m12" = "Dec %1$@, %2$@ at %3$@"; "Wallet.Time.PreciseDate_m12" = "Dec %1$@, %2$@ at %3$@";
"Wallet.VoiceOver.Editing.ClearText" = "Clear text"; "Wallet.VoiceOver.Editing.ClearText" = "Clear text";
"Wallet.Receive.ShareInvoiceUrlInfo" = "Share this link with other Gram wallet owners to receive %@ Grams from them."; "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.labelArrowNode = updatedLabelArrowNode
strongSelf.containerNode.addSubnode(updatedLabelArrowNode) strongSelf.containerNode.addSubnode(updatedLabelArrowNode)
if let image = updatedLabelArrowNode.image { 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) transition.updateFrame(node: updatedLabelArrowNode, frame: labelArrowNodeFrame)
rightLabelInset += 19.0 rightLabelInset += 19.0
} }
@ -775,10 +775,10 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
let badgeWidth = max(badgeDiameter, labelLayout.size.width + 10.0) let badgeWidth = max(badgeDiameter, labelLayout.size.width + 10.0)
let labelFrame: CGRect let labelFrame: CGRect
if case .badge = item.label { 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 strongSelf.labelNode.frame = labelFrame
} else { } 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) transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame)
} }

View File

@ -45,7 +45,7 @@ typedef enum {
@property (nonatomic, strong) NSString *recipientName; @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(^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); @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(^finishedTransitionOut)(void);
@property (nonatomic, copy) void(^customPresentOverlayController)(TGOverlayController *(^)(id<LegacyComponentsContext>)); @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;
- (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 intent:(TGCameraControllerIntent)intent;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia camera:(PGCamera *)camera previewView:(TGCameraPreviewView *)previewView 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/TGTimerTarget.h>
#import <LegacyComponents/TGMenuSheetController.h> #import <LegacyComponents/TGMenuSheetController.h>
#import <LegacyComponents/TGMediaPickerSendActionSheetController.h>
#import "TGMediaPickerGallerySelectedItemsModel.h" #import "TGMediaPickerGallerySelectedItemsModel.h"
#import "TGCameraCapturedPhoto.h" #import "TGCameraCapturedPhoto.h"
@ -1251,6 +1252,128 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
model.controller = galleryController; model.controller = galleryController;
model.suggestionContext = self.suggestionContext; 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) model.willFinishEditingItem = ^(id<TGMediaEditableItem> editableItem, id<TGMediaEditAdjustments> adjustments, id representation, bool hasChanges)
{ {
__strong TGCameraController *strongSelf = weakSelf; __strong TGCameraController *strongSelf = weakSelf;
@ -1279,9 +1402,6 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
model.interfaceView.hasSwipeGesture = false; model.interfaceView.hasSwipeGesture = false;
galleryController.model = model; galleryController.model = model;
__weak TGModernGalleryController *weakGalleryController = galleryController;
__weak TGMediaPickerGalleryModel *weakModel = model;
if (_items.count > 1) if (_items.count > 1)
[model.interfaceView updateSelectionInterface:selectionContext.count counterVisible:(selectionContext.count > 0) animated:false]; [model.interfaceView updateSelectionInterface:selectionContext.count counterVisible:(selectionContext.count > 0) animated:false];
else else
@ -1318,7 +1438,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
[[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"]; [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
if (strongSelf.finishedWithResults != nil) 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) if (strongSelf->_shortcut)
return; return;

View File

@ -135,11 +135,19 @@
- (NSString *)fileName - (NSString *)fileName
{ {
if (self.backingAsset != nil) if (self.backingAsset != nil) {
return [self.backingAsset valueForKey:@"filename"]; NSString *fileName = [self.backingAsset valueForKey:@"filename"];
else if (self.backingLegacyAsset != nil) 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 self.backingLegacyAsset.defaultRepresentation.filename;
}
return nil; return nil;
} }

View File

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

View File

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

View File

@ -382,7 +382,7 @@
[strongCameraView attachPreviewViewAnimated:true]; [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; __strong TGMenuSheetController *strongMenuController = weakMenuController;
if (strongMenuController == nil) if (strongMenuController == nil)

View File

@ -148,34 +148,36 @@ class ItemListCallListItemNode: ListViewItemNode {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let currentItem = self.item let currentItem = self.item
return { item, params, neighbors in return { [weak self] item, params, neighbors in
if self.callNodes.count != item.messages.count { if let strongSelf = self, strongSelf.callNodes.count != item.messages.count {
for pair in self.callNodes { for pair in strongSelf.callNodes {
pair.0.removeFromSupernode() pair.0.removeFromSupernode()
pair.1.removeFromSupernode() pair.1.removeFromSupernode()
} }
self.callNodes = [] strongSelf.callNodes = []
for _ in item.messages { for _ in item.messages {
let timeNode = TextNode() let timeNode = TextNode()
timeNode.isUserInteractionEnabled = false timeNode.isUserInteractionEnabled = false
self.addSubnode(timeNode) strongSelf.addSubnode(timeNode)
let typeNode = TextNode() let typeNode = TextNode()
typeNode.isUserInteractionEnabled = false 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))] = [] var makeNodesLayout: [((TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode))] = []
for nodes in self.callNodes { if let strongSelf = self {
for nodes in strongSelf.callNodes {
let makeTimeLayout = TextNode.asyncLayout(nodes.0) let makeTimeLayout = TextNode.asyncLayout(nodes.0)
let makeTypeLayout = TextNode.asyncLayout(nodes.1) let makeTypeLayout = TextNode.asyncLayout(nodes.1)
makeNodesLayout.append((makeTimeLayout, makeTypeLayout)) makeNodesLayout.append((makeTimeLayout, makeTypeLayout))
} }
}
var updatedTheme: PresentationTheme? 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 theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let value: AutomaticDownloadDataUsage 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 backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode

View File

@ -49,7 +49,7 @@ private func sizeValue(for sliderValue: CGFloat) -> Int32 {
return 0 return 0
} }
class AutodownloadSizeLimitItem: ListViewItem, ItemListItem { final class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
let theme: PresentationTheme let theme: PresentationTheme
let decimalSeparator: String let decimalSeparator: String
let text: 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 backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode

View File

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

View File

@ -485,6 +485,9 @@ func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndSt
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
let cacheUsagePromise = Promise<CacheUsageStatsResult?>()
cacheUsagePromise.set(cacheUsageStats(context: context))
let dataAndStorageDataPromise = Promise<DataAndStorageData>() let dataAndStorageDataPromise = Promise<DataAndStorageData>()
dataAndStorageDataPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings, ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings, ApplicationSpecificSharedDataKeys.voiceCallSettings, SharedDataKeys.proxySettings]) dataAndStorageDataPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings, ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings, ApplicationSpecificSharedDataKeys.voiceCallSettings, SharedDataKeys.proxySettings])
|> map { sharedData -> DataAndStorageData in |> map { sharedData -> DataAndStorageData in
@ -526,7 +529,7 @@ func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndSt
}) })
let arguments = DataAndStorageControllerArguments(openStorageUsage: { let arguments = DataAndStorageControllerArguments(openStorageUsage: {
pushControllerImpl?(storageUsageController(context: context)) pushControllerImpl?(storageUsageController(context: context, cacheUsagePromise: cacheUsagePromise))
}, openNetworkUsage: { }, openNetworkUsage: {
pushControllerImpl?(networkUsageStatsController(context: context)) pushControllerImpl?(networkUsageStatsController(context: context))
}, openProxy: { }, 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 case add
} }
class ProxySettingsActionItem: ListViewItem, ItemListItem { final class ProxySettingsActionItem: ListViewItem, ItemListItem {
let theme: PresentationTheme let theme: PresentationTheme
let title: String let title: String
let icon: ProxySettingsActionIcon let icon: ProxySettingsActionIcon
@ -77,7 +77,7 @@ class ProxySettingsActionItem: ListViewItem, ItemListItem {
private let titleFont = Font.regular(17.0) private let titleFont = Font.regular(17.0)
class ProxySettingsActionItemNode: ListViewItemNode { private final class ProxySettingsActionItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: 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 titleFont = Font.regular(17.0)
private let statusFont = Font.regular(14.0) private let statusFont = Font.regular(14.0)
class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode

View File

@ -7,6 +7,7 @@ import TelegramCore
import SyncCore import SyncCore
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences import TelegramUIPreferences
import TelegramStringFormatting
import ItemListUI import ItemListUI
import PresentationDataUtils import PresentationDataUtils
import OverlayStatusController import OverlayStatusController
@ -15,48 +16,67 @@ import ItemListPeerItem
import DeleteChatPeerActionSheetItem import DeleteChatPeerActionSheetItem
import UndoUI 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 { private final class StorageUsageControllerArguments {
let account: Account let account: Account
let updateKeepMedia: () -> Void let updateKeepMediaTimeout: (Int32) -> Void
let openClearAll: () -> Void let openClearAll: () -> Void
let openPeerMedia: (PeerId) -> 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.account = account
self.updateKeepMedia = updateKeepMedia self.updateKeepMediaTimeout = updateKeepMediaTimeout
self.openClearAll = openClearAll self.openClearAll = openClearAll
self.openPeerMedia = openPeerMedia self.openPeerMedia = openPeerMedia
self.clearPeerMedia = clearPeerMedia
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
} }
} }
private enum StorageUsageSection: Int32 { private enum StorageUsageSection: Int32 {
case keepMedia case keepMedia
case immutableSize case storage
case all
case peers case peers
} }
private enum StorageUsageEntry: ItemListNodeEntry { private enum StorageUsageEntry: ItemListNodeEntry {
case keepMedia(PresentationTheme, String, String) case keepMediaHeader(PresentationTheme, String)
case keepMedia(PresentationTheme, PresentationStrings, Int32)
case keepMediaInfo(PresentationTheme, String) case keepMediaInfo(PresentationTheme, String)
case storageHeader(PresentationTheme, String)
case storageUsage(PresentationTheme, PresentationDateTimeFormat, [StorageUsageCategory])
case collecting(PresentationTheme, String) case collecting(PresentationTheme, String)
case clearAll(PresentationTheme, String, Bool)
case immutableSize(PresentationTheme, String, String)
case clearAll(PresentationTheme, String, String, Bool)
case peersHeader(PresentationTheme, String) 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 { var section: ItemListSectionId {
switch self { switch self {
case .keepMedia, .keepMediaInfo: case .keepMediaHeader, .keepMedia, .keepMediaInfo:
return StorageUsageSection.keepMedia.rawValue return StorageUsageSection.keepMedia.rawValue
case .immutableSize: case .storageHeader, .storageUsage, .collecting, .clearAll:
return StorageUsageSection.immutableSize.rawValue return StorageUsageSection.storage.rawValue
case .collecting, .clearAll:
return StorageUsageSection.all.rawValue
case .peersHeader, .peer: case .peersHeader, .peer:
return StorageUsageSection.peers.rawValue return StorageUsageSection.peers.rawValue
} }
@ -64,27 +84,37 @@ private enum StorageUsageEntry: ItemListNodeEntry {
var stableId: Int32 { var stableId: Int32 {
switch self { switch self {
case .keepMedia: case .keepMediaHeader:
return 0 return 0
case .keepMediaInfo: case .keepMedia:
return 1 return 1
case .collecting: case .keepMediaInfo:
return 2 return 2
case .immutableSize: case .storageHeader:
return 3 return 3
case .clearAll: case .storageUsage:
return 4 return 4
case .peersHeader: case .collecting:
return 5 return 5
case let .peer(index, _, _, _, _, _, _, _): case .clearAll:
return 6 + index return 6
case .peersHeader:
return 7
case let .peer(index, _, _, _, _, _, _, _, _):
return 8 + index
} }
} }
static func ==(lhs: StorageUsageEntry, rhs: StorageUsageEntry) -> Bool { static func ==(lhs: StorageUsageEntry, rhs: StorageUsageEntry) -> Bool {
switch lhs { switch lhs {
case let .keepMedia(lhsTheme, lhsText, lhsValue): case let .keepMediaHeader(lhsTheme, lhsText):
if case let .keepMedia(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { 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 return true
} else { } else {
return false return false
@ -95,20 +125,26 @@ private enum StorageUsageEntry: ItemListNodeEntry {
} else { } else {
return false 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): case let .collecting(lhsTheme, lhsText):
if case let .collecting(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .collecting(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
} else { } else {
return false return false
} }
case let .immutableSize(lhsTheme, lhsText, lhsValue): case let .clearAll(lhsTheme, lhsText, lhsEnabled):
if case let .immutableSize(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .clearAll(rhsTheme, rhsText, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled {
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 {
return true return true
} else { } else {
return false return false
@ -119,8 +155,8 @@ private enum StorageUsageEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsChatPeer, lhsValue): case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsChatPeer, lhsValue, lhsRevealed):
if case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsChatPeer, rhsValue) = rhs { if case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsChatPeer, rhsValue, rhsRevealed) = rhs {
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
return false return false
} }
@ -145,6 +181,9 @@ private enum StorageUsageEntry: ItemListNodeEntry {
if lhsValue != rhsValue { if lhsValue != rhsValue {
return false return false
} }
if lhsRevealed != rhsRevealed {
return false
}
return true return true
} else { } else {
return false return false
@ -159,54 +198,62 @@ private enum StorageUsageEntry: ItemListNodeEntry {
func item(_ arguments: Any) -> ListViewItem { func item(_ arguments: Any) -> ListViewItem {
let arguments = arguments as! StorageUsageControllerArguments let arguments = arguments as! StorageUsageControllerArguments
switch self { switch self {
case let .keepMedia(theme, text, value): case let .keepMediaHeader(theme, text):
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: { return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
arguments.updateKeepMedia() 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): case let .keepMediaInfo(theme, text):
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section) 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): case let .collecting(theme, text):
return CalculatingCacheSizeItem(theme: theme, title: text, sectionId: self.section, style: .blocks) return CalculatingCacheSizeItem(theme: theme, title: text, sectionId: self.section, style: .blocks)
case let .immutableSize(theme, title, value): case let .clearAll(theme, text, enabled):
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) 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): case let .peersHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .clearAll(theme, text, value, enabled): case let .peer(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, chatPeer, value, revealed):
return ItemListDisclosureItem(theme: theme, icon: nil, title: text, enabled: enabled, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { var options: [ItemListPeerItemRevealOption] = [ItemListPeerItemRevealOption(type: .destructive, title: strings.ClearCache_Clear, action: {
arguments.openClearAll() arguments.clearPeerMedia(peer.id)
}) })]
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: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
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: {
let resolvedPeer = chatPeer ?? peer let resolvedPeer = chatPeer ?? peer
arguments.openPeerMedia(resolvedPeer.id) arguments.openPeerMedia(resolvedPeer.id)
}, setPeerIdWithRevealedOptions: { previousId, id in }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setPeerIdWithRevealedOptions(peerId, fromPeerId)
}, removePeer: { _ in }, removePeer: { _ in
}) })
} }
} }
} }
private func stringForKeepMediaTimeout(strings: PresentationStrings, timeout: Int32) -> String { private struct StoragUsageState: Equatable {
if timeout > 1 * 31 * 24 * 60 * 60 { let peerIdWithRevealedOptions: PeerId?
return strings.MessageTimer_Forever
} else { func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> StoragUsageState {
return timeIntervalString(strings: strings, value: timeout) 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] = [] 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)) entries.append(.keepMediaInfo(presentationData.theme, presentationData.strings.Cache_Help))
var addedHeader = false var addedHeader = false
entries.append(.storageHeader(presentationData.theme, presentationData.strings.ClearCache_StorageTitle(stringForDeviceType().uppercased()).0))
if let cacheStats = cacheStats, case let .result(stats) = cacheStats { 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 peerSizes: Int64 = 0
var statsByPeerId: [(PeerId, Int64)] = [] var statsByPeerId: [(PeerId, Int64)] = []
var peerIndices: [PeerId: Int] = [:] var peerIndices: [PeerId: Int] = [:]
@ -230,9 +277,27 @@ private func storageUsageControllerEntries(presentationData: PresentationData, c
peerSizes += combinedSize 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 var index: Int32 = 0
for (peerId, size) in statsByPeerId.sorted(by: { $0.1 > $1.1 }) { for (peerId, size) in statsByPeerId.sorted(by: { $0.1 > $1.1 }) {
@ -248,7 +313,7 @@ private func storageUsageControllerEntries(presentationData: PresentationData, c
chatPeer = mainPeer chatPeer = mainPeer
mainPeer = associatedPeer 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 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>() let cacheSettingsPromise = Promise<CacheStorageSettings>()
cacheSettingsPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings]) cacheSettingsPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings])
|> map { sharedData -> CacheStorageSettings in |> map { sharedData -> CacheStorageSettings in
@ -289,59 +375,27 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
var presentControllerImpl: ((ViewController, PresentationContextType, Any?) -> Void)? var presentControllerImpl: ((ViewController, PresentationContextType, Any?) -> Void)?
let statsPromise = Promise<CacheUsageStatsResult?>() var statsPromise: Promise<CacheUsageStatsResult?>
let resetStats: () -> Void = { if let cacheUsagePromise = cacheUsagePromise {
let containerPath = context.sharedContext.applicationBindings.containerPath statsPromise = cacheUsagePromise
let additionalPaths: [String] = [ } else {
NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0], statsPromise = Promise<CacheUsageStatsResult?>()
containerPath + "/Documents/files", statsPromise.set(cacheUsageStats(context: context))
containerPath + "/Documents/video", }
containerPath + "/Documents/audio",
containerPath + "/Documents/mediacache", let resetStats: () -> Void = {
containerPath + "/Documents/tempcache_v1/store", statsPromise.set(cacheUsageStats(context: context))
]
statsPromise.set(.single(nil)
|> then(collectCacheUsageStats(account: context.account, additionalCachePaths: additionalPaths, logFilesPath: context.sharedContext.applicationBindings.containerPath + "/telegram-data/logs")
|> map(Optional.init)))
} }
resetStats()
let actionDisposables = DisposableSet() let actionDisposables = DisposableSet()
let clearDisposable = MetaDisposable() let clearDisposable = MetaDisposable()
actionDisposables.add(clearDisposable) actionDisposables.add(clearDisposable)
let arguments = StorageUsageControllerArguments(account: context.account, updateKeepMedia: { let arguments = StorageUsageControllerArguments(account: context.account, updateKeepMediaTimeout: { value in
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 let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
return current.withUpdatedDefaultCacheStorageTimeout(timeout) return current.withUpdatedDefaultCacheStorageTimeout(value)
}).start() }).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))
}, openClearAll: { }, openClearAll: {
let _ = (statsPromise.get() let _ = (statsPromise.get()
|> take(1) |> take(1)
@ -535,8 +589,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
clearDisposable.set((signal clearDisposable.set((signal
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
statsPromise.set(.single(.result(resultStats))) 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))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in }), .current, nil)
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)
})) }))
} }
@ -717,8 +770,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
clearDisposable.set((signal clearDisposable.set((signal
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
statsPromise.set(.single(.result(resultStats))) 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))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in }), .current, nil)
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)
})) }))
} }
@ -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)? var dismissImpl: (() -> Void)?
let signal = combineLatest(context.sharedContext.presentationData, cacheSettingsPromise.get(), statsPromise.get()) |> deliverOnMainQueue let signal = combineLatest(context.sharedContext.presentationData, cacheSettingsPromise.get(), statsPromise.get(), statePromise.get()) |> deliverOnMainQueue
|> map { presentationData, cacheSettings, cacheStats -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, cacheSettings, cacheStats, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
let leftNavigationButton = isModal ? ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { let leftNavigationButton = isModal ? ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?() dismissImpl?()
}) : nil }) : 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 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)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> 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 PhotoResources
import OpenInExternalAppUI import OpenInExternalAppUI
public class WebBrowserItem: ListViewItem, ItemListItem { class WebBrowserItem: ListViewItem, ItemListItem {
let account: Account let account: Account
let theme: PresentationTheme let theme: PresentationTheme
let title: String let title: String
@ -71,7 +71,7 @@ public class WebBrowserItem: ListViewItem, ItemListItem {
private let titleFont = Font.regular(17.0) private let titleFont = Font.regular(17.0)
public class WebBrowserItemNode: ListViewItemNode { private final class WebBrowserItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode

View File

@ -9,10 +9,13 @@ public func fetchHttpResource(url: String) -> Signal<MediaResourceDataFetchResul
return Signal { subscriber in return Signal { subscriber in
subscriber.putNext(.reset) subscriber.putNext(.reset)
let disposable = signal.start(next: { next in let disposable = signal.start(next: { next in
let data = next as! Data if let response = next as? MTHttpResponse {
let fetchResult: MediaResourceDataFetchResult = .dataPart(resourceOffset: 0, data: data, range: 0 ..< data.count, complete: true) let fetchResult: MediaResourceDataFetchResult = .dataPart(resourceOffset: 0, data: response.data, range: 0 ..< response.data.count, complete: true)
subscriber.putNext(fetchResult) subscriber.putNext(fetchResult)
subscriber.putCompletion() subscriber.putCompletion()
} else {
subscriber.putError(.generic)
}
}, error: { _ in }, error: { _ in
subscriber.putError(.generic) subscriber.putError(.generic)
}, completed: { }, 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 { guard let strongSelf = self, strongSelf.beginMediaRecordingRequestId == requestId else {
return return
} }
guard checkAvailableDiskSpace(context: strongSelf.context, present: { [weak self] c, a in guard checkAvailableDiskSpace(context: strongSelf.context, push: { [weak self] c in
self?.present(c, in: .window(.root), with: a) self?.push(c)
}) else { }) else {
return return
} }
@ -5292,8 +5292,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
disposable.set((signal disposable.set((signal
|> deliverOnMainQueue).start(completed: { [weak self] in |> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self, let layout = strongSelf.validLayout { 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))", stringForDeviceType()).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))", deviceName).0), elevatedLayout: true, action: { _ in }), in: .current)
} }
})) }))
@ -5457,12 +5456,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}) })
}, openCamera: { [weak self] cameraView, menuController in }, openCamera: { [weak self] cameraView, menuController in
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { 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 let strongSelf = self {
if editMediaOptions != nil { if editMediaOptions != nil {
strongSelf.editMessageMediaWithLegacySignals(signals!) strongSelf.editMessageMediaWithLegacySignals(signals!)
} else { } else {
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: false) strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime)
} }
if !inputText.string.isEmpty { if !inputText.string.isEmpty {
//strongSelf.clearInputText() //strongSelf.clearInputText()

View File

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

View File

@ -7,7 +7,7 @@ import AlertUI
import PresentationDataUtils import PresentationDataUtils
import SettingsUI import SettingsUI
func totalDiskSpace() -> Int64 { private func totalDiskSpace() -> Int64 {
do { do {
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String) let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
return (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? 0 return (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? 0
@ -16,7 +16,7 @@ func totalDiskSpace() -> Int64 {
} }
} }
func freeDiskSpace() -> Int64 { private func freeDiskSpace() -> Int64 {
do { do {
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String) let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
return (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0 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 { guard freeDiskSpace() < threshold else {
return true return true
} }
let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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 = 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) push(storageUsageController(context: context, isModal: true))
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
present(controller, nil) push(controller)
return false return false
} }

View File

@ -11,7 +11,7 @@ import ShareController
import LegacyUI import LegacyUI
import LegacyMediaPickerUI 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 presentationData = context.sharedContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
@ -29,6 +29,12 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
controller = TGCameraController() controller = TGCameraController()
} }
controller.presentScheduleController = { done in
presentSchedulePicker { time in
done?(time)
}
}
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
} else { } else {
controller.customPresentOverlayController = { [weak legacyController] generateController in controller.customPresentOverlayController = { [weak legacyController] generateController in
@ -99,10 +105,10 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
legacyController?.dismiss() 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 { 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()) 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) menuController?.dismiss(animated: false)
@ -118,7 +124,7 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
description["timer"] = timer description["timer"] = timer
} }
if let item = legacyAssetPickerItemGenerator()(description, caption, entities, nil) { 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 description["timer"] = timer
} }
if let item = legacyAssetPickerItemGenerator()(description, caption, entities, nil) { if let item = legacyAssetPickerItemGenerator()(description, caption, entities, nil) {
sendMessagesWithSignals([SSignal.single(item)]) sendMessagesWithSignals([SSignal.single(item)], false, 0)
} }
} }
menuController?.dismiss(animated: false) menuController?.dismiss(animated: false)
@ -193,7 +199,7 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B
legacyController?.dismiss() 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 { if let selectionContext = selectionContext, let editingContext = editingContext {
let signals = TGCameraController.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, storeAssets: saveCapturedMedia, saveEditedPhotos: saveEditedPhotos, descriptionGenerator: legacyAssetPickerItemGenerator()) let signals = TGCameraController.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, storeAssets: saveCapturedMedia, saveEditedPhotos: saveEditedPhotos, descriptionGenerator: legacyAssetPickerItemGenerator())
if let parentController = parentController { if let parentController = parentController {
@ -218,77 +224,7 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B
}), showInChat: nil, externalShare: false), in: .window(.root)) }), showInChat: nil, externalShare: false), in: .window(.root))
} }
} }
//legacyController?.dismissWithAnimation()
} }
parentController.present(legacyController, in: .window(.root)) 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 didPlayToEndTimeObserver: NSObjectProtocol?
private var timeObserver: Any? private var timeObserver: Any?
private var seekId: Int = 0
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, url: String, imageReference: ImageMediaReference, intrinsicDimensions: CGSize, approximateDuration: Int32) { init(postbox: Postbox, audioSessionManager: ManagedAudioSession, url: String, imageReference: ImageMediaReference, intrinsicDimensions: CGSize, approximateDuration: Int32) {
self.audioSessionManager = audioSessionManager self.audioSessionManager = audioSessionManager
@ -131,7 +133,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
guard let strongSelf = self else { guard let strongSelf = self else {
return 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) strongSelf._status.set(strongSelf.statusValue)
} }
} }
@ -171,7 +173,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
} else { } else {
status = isPlaying ? .playing : .paused 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) self._status.set(self.statusValue)
} else if keyPath == "playbackBufferEmpty" { } else if keyPath == "playbackBufferEmpty" {
let isPlaying = !self.player.rate.isZero let isPlaying = !self.player.rate.isZero
@ -182,7 +184,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
} else { } else {
status = isPlaying ? .playing : .paused 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) self._status.set(self.statusValue)
} else if keyPath == "playbackLikelyToKeepUp" || keyPath == "playbackBufferFull" { } else if keyPath == "playbackLikelyToKeepUp" || keyPath == "playbackBufferFull" {
let isPlaying = !self.player.rate.isZero let isPlaying = !self.player.rate.isZero
@ -193,7 +195,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
} else { } else {
status = isPlaying ? .playing : .paused 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) self._status.set(self.statusValue)
if !self.didBeginPlaying { if !self.didBeginPlaying {
@ -217,7 +219,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
func play() { func play() {
assert(Queue.mainQueue().isCurrent()) assert(Queue.mainQueue().isCurrent())
if !self.initializedStatus { 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 { if !self.hasAudioSession {
self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play, activate: { [weak self] _ in self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play, activate: { [weak self] _ in
@ -236,7 +238,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
func pause() { func pause() {
assert(Queue.mainQueue().isCurrent()) assert(Queue.mainQueue().isCurrent())
if !self.initializedStatus { 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() self.player.pause()
} }
@ -256,6 +258,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
func seek(_ timestamp: Double) { func seek(_ timestamp: Double) {
assert(Queue.mainQueue().isCurrent()) assert(Queue.mainQueue().isCurrent())
self.seekId += 1
self.playerItem.seek(to: CMTimeMake(value: Int64(timestamp) * 1000, timescale: 1000)) 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 _s: [Int: String]
private let _r: [Int: [(Int, NSRange)]] private let _r: [Int: [(Int, NSRange)]]
private let _ps: [Int: String] private let _ps: [Int: String]
public var Wallet_Updated_JustNow: String { return self._s[0]! } public var Wallet_Created_ExportErrorText: String { return self._s[0]! }
public var Wallet_WordCheck_IncorrectText: String { return self._s[1]! } public var Wallet_Send_ConfirmationConfirm: String { return self._s[2]! }
public var Wallet_Month_ShortNovember: String { return self._s[2]! } public var Wallet_Month_GenJuly: String { return self._s[3]! }
public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[3]! } public var Wallet_Month_GenDecember: String { return self._s[4]! }
public var Wallet_Info_Send: String { return self._s[4]! } public var Wallet_Month_ShortJanuary: String { return self._s[5]! }
public var Wallet_TransactionInfo_SendGrams: String { return self._s[5]! } public var Wallet_WordCheck_Title: String { return self._s[6]! }
public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) { public var Wallet_Month_ShortMarch: String { return self._s[7]! }
return formatWithArgumentRanges(self._s[6]!, self._r[6]!, [_0]) 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_AmountText: String { return self._s[19]! }
public var Wallet_Receive_ShareUrlInfo: String { return self._s[8]! } public var Wallet_TransactionInfo_CommentHeader: String { return self._s[20]! }
public var Wallet_RestoreFailed_Title: String { return self._s[9]! } public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) {
public var Wallet_TransactionInfo_CopyAddress: String { return self._s[11]! } return formatWithArgumentRanges(self._s[21]!, self._r[21]!, [_0])
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_Send_SyncInProgress: String { return self._s[18]! } public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[22]! }
public var Wallet_Info_YourBalance: String { return self._s[19]! } public var Wallet_SecureStorageReset_Title: String { return self._s[23]! }
public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[20]! } public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[24]! }
public var Wallet_TransactionInfo_CommentHeader: String { return self._s[21]! } public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[25]! }
public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[22]! } public var Wallet_Month_GenApril: String { return self._s[26]! }
public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { public var Wallet_AccessDenied_Settings: String { return self._s[27]! }
return formatWithArgumentRanges(self._s[23]!, self._r[23]!, [_1, _2, _3]) public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[28]! }
} public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[29]! }
public var Wallet_Settings_ConfigurationInfo: String { return self._s[24]! } public var Wallet_Qr_Title: String { return self._s[30]! }
public var Wallet_WordImport_IncorrectText: String { return self._s[25]! } public var Wallet_Intro_ImportExisting: String { return self._s[31]! }
public var Wallet_Month_GenJanuary: String { return self._s[26]! } public var Wallet_Send_OwnAddressAlertText: String { return self._s[32]! }
public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[27]! } public var Wallet_Month_GenAugust: String { return self._s[33]! }
public var Wallet_Receive_ShareAddress: String { return self._s[28]! } public var Wallet_Month_ShortDecember: String { return self._s[34]! }
public var Wallet_WordImport_Title: String { return self._s[29]! } public var Wallet_Info_Receive: String { return self._s[35]! }
public var Wallet_TransactionInfo_Title: String { return self._s[30]! } public var Wallet_Send_Send: String { return self._s[36]! }
public var Wallet_Words_NotDoneText: String { return self._s[32]! } public var Wallet_RestoreFailed_Text: String { return self._s[37]! }
public var Wallet_RestoreFailed_EnterWords: String { return self._s[33]! } public var Wallet_Navigation_Cancel: String { return self._s[38]! }
public var Wallet_WordImport_Text: String { return self._s[34]! } public var Wallet_CreateInvoice_Title: String { return self._s[39]! }
public var Wallet_RestoreFailed_Text: String { return self._s[36]! } public var Wallet_Sent_Title: String { return self._s[40]! }
public var Wallet_TransactionInfo_NoAddress: String { return self._s[37]! } public var Wallet_WordCheck_Continue: String { return self._s[41]! }
public var Wallet_Navigation_Back: String { return self._s[38]! } public var Wallet_Send_SyncInProgress: String { return self._s[43]! }
public var Wallet_Intro_Terms: String { return self._s[39]! }
public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) { 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)]) { public var Wallet_Month_GenMay: String { return self._s[45]! }
return formatWithArgumentRanges(self._s[41]!, self._r[41]!, [_1, _2, _3]) public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[46]! }
} public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[47]! }
public var Wallet_TransactionInfo_AddressCopied: String { return self._s[42]! } 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)]) { 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)]) { 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]) return formatWithArgumentRanges(self._s[86]!, self._r[86]!, [_0])
} }
public var Wallet_Send_SendAnyway: String { return self._s[87]! } public var Wallet_Send_AmountText: String { return self._s[87]! }
public var Wallet_UnknownError: String { return self._s[88]! } public var Wallet_Info_TransactionTo: String { return self._s[88]! }
public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[89]! } public var Wallet_Words_Done: String { return self._s[89]! }
public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[90]! } public var Wallet_Created_Text: String { return self._s[90]! }
public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[92]! } public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[91]! }
public var Wallet_Configuration_SourceInfo: String { return self._s[93]! } public var Wallet_Month_ShortJuly: String { return self._s[92]! }
public var Wallet_Words_NotDoneOk: String { return self._s[94]! } public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) {
public var Wallet_Intro_Title: String { return self._s[95]! } return formatWithArgumentRanges(self._s[93]!, self._r[93]!, [_0])
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_Receive_ShareInvoiceUrl: String { return self._s[101]! } public var Wallet_Created_Title: String { return self._s[94]! }
public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { 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]) 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 var Wallet_Send_UninitializedText: String { return self._s[104]! }
public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) { public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[105]! }
return formatWithArgumentRanges(self._s[105]!, self._r[105]!, [_0]) public var Wallet_Send_ErrorInvalidAddress: String { return self._s[106]! }
} public var Wallet_Words_NotDoneTitle: String { return self._s[107]! }
public var Wallet_Month_GenNovember: String { return self._s[106]! } public var Wallet_Navigation_Back: String { return self._s[108]! }
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 func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { 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_UnknownTransaction: String { return self._s[110]! }
public var Wallet_Info_RefreshErrorText: String { return self._s[214]! } public var Wallet_RestoreFailed_Title: String { return self._s[111]! }
public var Wallet_SecureStorageReset_Title: String { return self._s[215]! } public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) {
public var Wallet_Receive_CommentHeader: String { return self._s[216]! } return formatWithArgumentRanges(self._s[112]!, self._r[112]!, [_0])
public var Wallet_Info_ReceiveGrams: String { return self._s[217]! } }
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { 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 form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue) 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 form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue) 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 { guard let strongSelf = self else {
return return
} }
if node.isLast { if let index = strongSelf.inputNodes.firstIndex(where: { $0 === node }) {
if index == strongSelf.inputNodes.count - 1 {
if done { if done {
action() action()
} else {
strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -20.0), animated: true)
} }
} else { } else {
if let index = strongSelf.inputNodes.firstIndex(where: { $0 === node }), index != strongSelf.inputNodes.count - 1 {
strongSelf.inputNodes[index + 1].focus() strongSelf.inputNodes[index + 1].focus()
} }
} }
@ -2749,7 +2747,9 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
return return
} }
if node.isLast { 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 { } else {
strongSelf.scrollNode.view.scrollRectToVisible(node.frame.insetBy(dx: 0.0, dy: -10.0), animated: true) strongSelf.scrollNode.view.scrollRectToVisible(node.frame.insetBy(dx: 0.0, dy: -10.0), animated: true)
} }