mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-01 16:06:59 +00:00
Merge commit 'b35b94a6e74a8e3422e555fe0c171dbab119fe94' into beta
This commit is contained in:
commit
978765b720
@ -5075,5 +5075,13 @@ Any member of this group will be able to see messages in the channel.";
|
||||
|
||||
"Group.ErrorSupergroupConversionNotPossible" = "Sorry, you are a member of too many groups and channels. Please leave some before creating a new one.";
|
||||
|
||||
"ClearCache.StorageTitle" = "%@ STORAGE";
|
||||
"ClearCache.StorageCache" = "Telegram Cache";
|
||||
"ClearCache.StorageServiceFiles" = "Telegram Service Files";
|
||||
"ClearCache.StorageOtherApps" = "Other Apps";
|
||||
"ClearCache.StorageFree" = "Free";
|
||||
"ClearCache.ClearCache" = "Clear Telegram Cache";
|
||||
"ClearCache.Clear" = "Clear";
|
||||
|
||||
"ChatList.DeletedChats_1" = "Deleted 1 chat";
|
||||
"ChatList.DeletedChats_any" = "Deleted %@ chats";
|
||||
|
@ -402,10 +402,19 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
|
||||
}
|
||||
|
||||
func authorizeAccessToCamera(completion: @escaping () -> Void) {
|
||||
AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in
|
||||
AVCaptureDevice.requestAccess(for: AVMediaType.video) { [weak self] response in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
Queue.mainQueue().async {
|
||||
if response {
|
||||
completion()
|
||||
} else {
|
||||
let presentationData = strongSelf.presentationData
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.Wallet_AccessDenied_Title, text: presentationData.strings.Wallet_AccessDenied_Camera, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Wallet_Intro_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Wallet_AccessDenied_Settings, action: {
|
||||
strongSelf.openPlatformSettings()
|
||||
})])
|
||||
strongSelf.window.present(controller, on: .root)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -540,7 +549,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
let presentationData = WalletPresentationData(
|
||||
theme: WalletTheme(
|
||||
info: WalletInfoTheme(
|
||||
buttonBackgroundColor: accentColor,
|
||||
buttonBackgroundColor: UIColor(rgb: 0x32aafe),
|
||||
buttonTextColor: .white,
|
||||
incomingFundsTitleColor: UIColor(rgb: 0x00b12c),
|
||||
outgoingFundsTitleColor: UIColor(rgb: 0xff3b30)
|
||||
|
@ -218,3 +218,6 @@
|
||||
"Wallet.Time.PreciseDate_m12" = "Dec %1$@, %2$@ at %3$@";
|
||||
"Wallet.VoiceOver.Editing.ClearText" = "Clear text";
|
||||
"Wallet.Receive.ShareInvoiceUrlInfo" = "Share this link with other Gram wallet owners to receive %@ Grams from them.";
|
||||
"Wallet.AccessDenied.Title" = "Please Allow Access";
|
||||
"Wallet.AccessDenied.Camera" = "TON Wallet needs access to camera to scan QR codes.\n\nPlease go to Settings > Privacy > Camera and set TON Wallet to ON.";
|
||||
"Wallet.AccessDenied.Settings" = "Settings";
|
||||
|
@ -763,7 +763,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
strongSelf.labelArrowNode = updatedLabelArrowNode
|
||||
strongSelf.containerNode.addSubnode(updatedLabelArrowNode)
|
||||
if let image = updatedLabelArrowNode.image {
|
||||
let labelArrowNodeFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightLabelInset - image.size.width, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
|
||||
let labelArrowNodeFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightLabelInset - image.size.width + 8.0, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
|
||||
transition.updateFrame(node: updatedLabelArrowNode, frame: labelArrowNodeFrame)
|
||||
rightLabelInset += 19.0
|
||||
}
|
||||
@ -775,10 +775,10 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
let badgeWidth = max(badgeDiameter, labelLayout.size.width + 10.0)
|
||||
let labelFrame: CGRect
|
||||
if case .badge = item.label {
|
||||
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: floor((contentSize.height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
||||
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
|
||||
strongSelf.labelNode.frame = labelFrame
|
||||
} else {
|
||||
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - rightLabelInset - rightInset, y: floor((contentSize.height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
||||
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - rightLabelInset - rightInset, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
|
||||
transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame)
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ typedef enum {
|
||||
|
||||
@property (nonatomic, strong) NSString *recipientName;
|
||||
|
||||
@property (nonatomic, copy) void(^finishedWithResults)(TGOverlayController *controller, TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem);
|
||||
@property (nonatomic, copy) void(^finishedWithResults)(TGOverlayController *controller, TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem, bool silentPosting, int32_t scheduleTime);
|
||||
@property (nonatomic, copy) void(^finishedWithPhoto)(TGOverlayController *controller, UIImage *resultImage, NSString *caption, NSArray *entities, NSArray *stickers, NSNumber *timer);
|
||||
@property (nonatomic, copy) void(^finishedWithVideo)(TGOverlayController *controller, NSURL *videoURL, UIImage *previewImage, NSTimeInterval duration, CGSize dimensions, TGVideoEditAdjustments *adjustments, NSString *caption, NSArray *entities, NSArray *stickers, NSNumber *timer);
|
||||
|
||||
@ -55,6 +55,8 @@ typedef enum {
|
||||
@property (nonatomic, copy) void(^finishedTransitionOut)(void);
|
||||
@property (nonatomic, copy) void(^customPresentOverlayController)(TGOverlayController *(^)(id<LegacyComponentsContext>));
|
||||
|
||||
@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t));
|
||||
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia;
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia intent:(TGCameraControllerIntent)intent;
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia camera:(PGCamera *)camera previewView:(TGCameraPreviewView *)previewView intent:(TGCameraControllerIntent)intent;
|
||||
|
@ -45,6 +45,7 @@
|
||||
#import <LegacyComponents/TGTimerTarget.h>
|
||||
|
||||
#import <LegacyComponents/TGMenuSheetController.h>
|
||||
#import <LegacyComponents/TGMediaPickerSendActionSheetController.h>
|
||||
|
||||
#import "TGMediaPickerGallerySelectedItemsModel.h"
|
||||
#import "TGCameraCapturedPhoto.h"
|
||||
@ -1251,6 +1252,128 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
model.controller = galleryController;
|
||||
model.suggestionContext = self.suggestionContext;
|
||||
|
||||
__weak TGModernGalleryController *weakGalleryController = galleryController;
|
||||
__weak TGMediaPickerGalleryModel *weakModel = model;
|
||||
|
||||
model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item) {
|
||||
__strong TGCameraController *strongSelf = weakSelf;
|
||||
__strong TGMediaPickerGalleryModel *strongModel = weakModel;
|
||||
if (strongSelf == nil || !(strongSelf.hasSilentPosting || strongSelf.hasSchedule) || strongSelf->_shortcut)
|
||||
return;
|
||||
|
||||
if (iosMajorVersion() >= 10) {
|
||||
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
|
||||
[generator impactOccurred];
|
||||
}
|
||||
|
||||
bool effectiveHasSchedule = strongSelf.hasSchedule;
|
||||
for (id item in strongModel.selectionContext.selectedItems)
|
||||
{
|
||||
if ([item isKindOfClass:[TGMediaAsset class]])
|
||||
{
|
||||
if ([[strongSelf->_editingContext timerForItem:item] integerValue] > 0)
|
||||
{
|
||||
effectiveHasSchedule = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TGMediaPickerSendActionSheetController *controller = [[TGMediaPickerSendActionSheetController alloc] initWithContext:strongSelf->_context sendButtonFrame:strongModel.interfaceView.doneButtonFrame canSendSilently:strongSelf->_hasSilentPosting canSchedule:effectiveHasSchedule];
|
||||
controller.send = ^{
|
||||
__strong TGCameraController *strongSelf = weakSelf;
|
||||
__strong TGMediaPickerGalleryModel *strongModel = weakModel;
|
||||
|
||||
if (strongSelf == nil || strongModel == nil)
|
||||
return;
|
||||
|
||||
__strong TGModernGalleryController *strongController = weakGalleryController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
|
||||
{
|
||||
TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
|
||||
[itemView stop];
|
||||
[itemView setPlayButtonHidden:true animated:true];
|
||||
}
|
||||
|
||||
if (strongSelf->_selectionContext.allowGrouping)
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
|
||||
|
||||
if (strongSelf.finishedWithResults != nil)
|
||||
strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, 0);
|
||||
|
||||
[strongSelf _dismissTransitionForResultController:strongController];
|
||||
};
|
||||
controller.sendSilently = ^{
|
||||
__strong TGCameraController *strongSelf = weakSelf;
|
||||
__strong TGMediaPickerGalleryModel *strongModel = weakModel;
|
||||
|
||||
if (strongSelf == nil || strongModel == nil)
|
||||
return;
|
||||
|
||||
__strong TGModernGalleryController *strongController = weakGalleryController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
|
||||
{
|
||||
TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
|
||||
[itemView stop];
|
||||
[itemView setPlayButtonHidden:true animated:true];
|
||||
}
|
||||
|
||||
if (strongSelf->_selectionContext.allowGrouping)
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
|
||||
|
||||
if (strongSelf.finishedWithResults != nil)
|
||||
strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, true, 0);
|
||||
|
||||
[strongSelf _dismissTransitionForResultController:strongController];
|
||||
};
|
||||
controller.schedule = ^{
|
||||
__strong TGCameraController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
strongSelf.presentScheduleController(^(int32_t time) {
|
||||
__strong TGCameraController *strongSelf = weakSelf;
|
||||
__strong TGMediaPickerGalleryModel *strongModel = weakModel;
|
||||
|
||||
if (strongSelf == nil || strongModel == nil)
|
||||
return;
|
||||
|
||||
__strong TGModernGalleryController *strongController = weakGalleryController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
|
||||
{
|
||||
TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
|
||||
[itemView stop];
|
||||
[itemView setPlayButtonHidden:true animated:true];
|
||||
}
|
||||
|
||||
if (strongSelf->_selectionContext.allowGrouping)
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
|
||||
|
||||
if (strongSelf.finishedWithResults != nil)
|
||||
strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, time);
|
||||
|
||||
[strongSelf _dismissTransitionForResultController:strongController];
|
||||
});
|
||||
};
|
||||
|
||||
id<LegacyComponentsOverlayWindowManager> windowManager = nil;
|
||||
id<LegacyComponentsContext> windowContext = nil;
|
||||
windowManager = [strongSelf->_context makeOverlayWindowManager];
|
||||
windowContext = [windowManager context];
|
||||
|
||||
TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:strongSelf contentController:(TGOverlayController *)controller];
|
||||
controllerWindow.hidden = false;
|
||||
};
|
||||
|
||||
model.willFinishEditingItem = ^(id<TGMediaEditableItem> editableItem, id<TGMediaEditAdjustments> adjustments, id representation, bool hasChanges)
|
||||
{
|
||||
__strong TGCameraController *strongSelf = weakSelf;
|
||||
@ -1278,9 +1401,6 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
|
||||
model.interfaceView.hasSwipeGesture = false;
|
||||
galleryController.model = model;
|
||||
|
||||
__weak TGModernGalleryController *weakGalleryController = galleryController;
|
||||
__weak TGMediaPickerGalleryModel *weakModel = model;
|
||||
|
||||
if (_items.count > 1)
|
||||
[model.interfaceView updateSelectionInterface:selectionContext.count counterVisible:(selectionContext.count > 0) animated:false];
|
||||
@ -1318,7 +1438,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
|
||||
|
||||
if (strongSelf.finishedWithResults != nil)
|
||||
strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset);
|
||||
strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, 0);
|
||||
|
||||
if (strongSelf->_shortcut)
|
||||
return;
|
||||
|
@ -135,11 +135,19 @@
|
||||
|
||||
- (NSString *)fileName
|
||||
{
|
||||
if (self.backingAsset != nil)
|
||||
return [self.backingAsset valueForKey:@"filename"];
|
||||
else if (self.backingLegacyAsset != nil)
|
||||
if (self.backingAsset != nil) {
|
||||
NSString *fileName = [self.backingAsset valueForKey:@"filename"];
|
||||
if (fileName == nil) {
|
||||
NSArray *resources = [PHAssetResource assetResourcesForAsset:self.backingAsset];
|
||||
PHAssetResource *resource = resources.firstObject;
|
||||
if (resource != nil) {
|
||||
fileName = resource.originalFilename;
|
||||
}
|
||||
}
|
||||
return fileName;
|
||||
} else if (self.backingLegacyAsset != nil) {
|
||||
return self.backingLegacyAsset.defaultRepresentation.filename;
|
||||
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
@ -295,6 +295,9 @@
|
||||
if (!found)
|
||||
fileName = asset.fileName;
|
||||
}
|
||||
if (fileName == nil) {
|
||||
fileName = asset.fileName;
|
||||
}
|
||||
|
||||
if (iosMajorVersion() >= 10 && [dataUTI rangeOfString:@"heic"].location != NSNotFound)
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
#import "TGMediaPickerModernGalleryMixin.h"
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
#import <LegacyComponents/LegacyComponents.h>
|
||||
|
||||
#import <LegacyComponents/TGModernGalleryController.h>
|
||||
@ -144,8 +146,10 @@
|
||||
if (strongSelf == nil || !(hasSilentPosting || hasSchedule))
|
||||
return;
|
||||
|
||||
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
|
||||
[generator impactOccurred];
|
||||
if (iosMajorVersion() >= 10) {
|
||||
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
|
||||
[generator impactOccurred];
|
||||
}
|
||||
|
||||
bool effectiveHasSchedule = hasSchedule;
|
||||
for (id item in strongSelf->_galleryModel.selectionContext.selectedItems)
|
||||
|
@ -382,7 +382,7 @@
|
||||
|
||||
[strongCameraView attachPreviewViewAnimated:true];
|
||||
};
|
||||
controller.finishedWithResults = ^(__unused TGOverlayController *controller, TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem)
|
||||
controller.finishedWithResults = ^(__unused TGOverlayController *controller, TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem, __unused bool silentPosting, __unused int32_t scheduleTime)
|
||||
{
|
||||
__strong TGMenuSheetController *strongMenuController = weakMenuController;
|
||||
if (strongMenuController == nil)
|
||||
|
@ -148,33 +148,35 @@ class ItemListCallListItemNode: ListViewItemNode {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, params, neighbors in
|
||||
if self.callNodes.count != item.messages.count {
|
||||
for pair in self.callNodes {
|
||||
return { [weak self] item, params, neighbors in
|
||||
if let strongSelf = self, strongSelf.callNodes.count != item.messages.count {
|
||||
for pair in strongSelf.callNodes {
|
||||
pair.0.removeFromSupernode()
|
||||
pair.1.removeFromSupernode()
|
||||
}
|
||||
|
||||
self.callNodes = []
|
||||
strongSelf.callNodes = []
|
||||
|
||||
for _ in item.messages {
|
||||
let timeNode = TextNode()
|
||||
timeNode.isUserInteractionEnabled = false
|
||||
self.addSubnode(timeNode)
|
||||
strongSelf.addSubnode(timeNode)
|
||||
|
||||
let typeNode = TextNode()
|
||||
typeNode.isUserInteractionEnabled = false
|
||||
self.addSubnode(typeNode)
|
||||
strongSelf.addSubnode(typeNode)
|
||||
|
||||
self.callNodes.append((timeNode, typeNode))
|
||||
strongSelf.callNodes.append((timeNode, typeNode))
|
||||
}
|
||||
}
|
||||
|
||||
var makeNodesLayout: [((TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode))] = []
|
||||
for nodes in self.callNodes {
|
||||
let makeTimeLayout = TextNode.asyncLayout(nodes.0)
|
||||
let makeTypeLayout = TextNode.asyncLayout(nodes.1)
|
||||
makeNodesLayout.append((makeTimeLayout, makeTypeLayout))
|
||||
if let strongSelf = self {
|
||||
for nodes in strongSelf.callNodes {
|
||||
let makeTimeLayout = TextNode.asyncLayout(nodes.0)
|
||||
let makeTypeLayout = TextNode.asyncLayout(nodes.1)
|
||||
makeNodesLayout.append((makeTimeLayout, makeTypeLayout))
|
||||
}
|
||||
}
|
||||
|
||||
var updatedTheme: PresentationTheme?
|
||||
|
@ -31,7 +31,7 @@ enum AutomaticDownloadDataUsage: Int {
|
||||
}
|
||||
}
|
||||
|
||||
class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem {
|
||||
final class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let value: AutomaticDownloadDataUsage
|
||||
@ -93,7 +93,7 @@ private func generateKnobImage() -> UIImage? {
|
||||
})
|
||||
}
|
||||
|
||||
class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
|
||||
private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
@ -49,7 +49,7 @@ private func sizeValue(for sliderValue: CGFloat) -> Int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
|
||||
final class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let decimalSeparator: String
|
||||
let text: String
|
||||
@ -109,7 +109,7 @@ private func generateKnobImage() -> UIImage? {
|
||||
})
|
||||
}
|
||||
|
||||
class AutodownloadSizeLimitItemNode: ListViewItemNode {
|
||||
private final class AutodownloadSizeLimitItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
@ -8,7 +8,7 @@ import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import ActivityIndicator
|
||||
|
||||
class CalculatingCacheSizeItem: ListViewItem, ItemListItem {
|
||||
final class CalculatingCacheSizeItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let sectionId: ItemListSectionId
|
||||
@ -57,7 +57,7 @@ class CalculatingCacheSizeItem: ListViewItem, ItemListItem {
|
||||
|
||||
private let titleFont = Font.regular(14.0)
|
||||
|
||||
class CalculatingCacheSizeItemNode: ListViewItemNode {
|
||||
private final class CalculatingCacheSizeItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
@ -485,6 +485,9 @@ func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndSt
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let cacheUsagePromise = Promise<CacheUsageStatsResult?>()
|
||||
cacheUsagePromise.set(cacheUsageStats(context: context))
|
||||
|
||||
let dataAndStorageDataPromise = Promise<DataAndStorageData>()
|
||||
dataAndStorageDataPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings, ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings, ApplicationSpecificSharedDataKeys.voiceCallSettings, SharedDataKeys.proxySettings])
|
||||
|> map { sharedData -> DataAndStorageData in
|
||||
@ -526,7 +529,7 @@ func dataAndStorageController(context: AccountContext, focusOnItemTag: DataAndSt
|
||||
})
|
||||
|
||||
let arguments = DataAndStorageControllerArguments(openStorageUsage: {
|
||||
pushControllerImpl?(storageUsageController(context: context))
|
||||
pushControllerImpl?(storageUsageController(context: context, cacheUsagePromise: cacheUsagePromise))
|
||||
}, openNetworkUsage: {
|
||||
pushControllerImpl?(networkUsageStatsController(context: context))
|
||||
}, openProxy: {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ enum ProxySettingsActionIcon {
|
||||
case add
|
||||
}
|
||||
|
||||
class ProxySettingsActionItem: ListViewItem, ItemListItem {
|
||||
final class ProxySettingsActionItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let icon: ProxySettingsActionIcon
|
||||
@ -77,7 +77,7 @@ class ProxySettingsActionItem: ListViewItem, ItemListItem {
|
||||
|
||||
private let titleFont = Font.regular(17.0)
|
||||
|
||||
class ProxySettingsActionItemNode: ListViewItemNode {
|
||||
private final class ProxySettingsActionItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
@ -101,7 +101,7 @@ final class ProxySettingsServerItem: ListViewItem, ItemListItem {
|
||||
private let titleFont = Font.regular(17.0)
|
||||
private let statusFont = Font.regular(14.0)
|
||||
|
||||
class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
|
||||
private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
@ -7,6 +7,7 @@ import TelegramCore
|
||||
import SyncCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import TelegramStringFormatting
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import OverlayStatusController
|
||||
@ -15,48 +16,67 @@ import ItemListPeerItem
|
||||
import DeleteChatPeerActionSheetItem
|
||||
import UndoUI
|
||||
|
||||
private func totalDiskSpace() -> Int64 {
|
||||
do {
|
||||
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
|
||||
return (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? 0
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private func freeDiskSpace() -> Int64 {
|
||||
do {
|
||||
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
|
||||
return (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private final class StorageUsageControllerArguments {
|
||||
let account: Account
|
||||
let updateKeepMedia: () -> Void
|
||||
let updateKeepMediaTimeout: (Int32) -> Void
|
||||
let openClearAll: () -> Void
|
||||
let openPeerMedia: (PeerId) -> Void
|
||||
let clearPeerMedia: (PeerId) -> Void
|
||||
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
||||
|
||||
init(account: Account, updateKeepMedia: @escaping () -> Void, openClearAll: @escaping () -> Void, openPeerMedia: @escaping (PeerId) -> Void) {
|
||||
init(account: Account, updateKeepMediaTimeout: @escaping (Int32) -> Void, openClearAll: @escaping () -> Void, openPeerMedia: @escaping (PeerId) -> Void, clearPeerMedia: @escaping (PeerId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) {
|
||||
self.account = account
|
||||
self.updateKeepMedia = updateKeepMedia
|
||||
self.updateKeepMediaTimeout = updateKeepMediaTimeout
|
||||
self.openClearAll = openClearAll
|
||||
self.openPeerMedia = openPeerMedia
|
||||
self.clearPeerMedia = clearPeerMedia
|
||||
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
||||
}
|
||||
}
|
||||
|
||||
private enum StorageUsageSection: Int32 {
|
||||
case keepMedia
|
||||
case immutableSize
|
||||
case all
|
||||
case storage
|
||||
case peers
|
||||
}
|
||||
|
||||
private enum StorageUsageEntry: ItemListNodeEntry {
|
||||
case keepMedia(PresentationTheme, String, String)
|
||||
case keepMediaHeader(PresentationTheme, String)
|
||||
case keepMedia(PresentationTheme, PresentationStrings, Int32)
|
||||
case keepMediaInfo(PresentationTheme, String)
|
||||
|
||||
case storageHeader(PresentationTheme, String)
|
||||
case storageUsage(PresentationTheme, PresentationDateTimeFormat, [StorageUsageCategory])
|
||||
case collecting(PresentationTheme, String)
|
||||
|
||||
case immutableSize(PresentationTheme, String, String)
|
||||
|
||||
case clearAll(PresentationTheme, String, String, Bool)
|
||||
case clearAll(PresentationTheme, String, Bool)
|
||||
|
||||
case peersHeader(PresentationTheme, String)
|
||||
case peer(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, Peer?, String)
|
||||
case peer(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, Peer?, String, Bool)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .keepMedia, .keepMediaInfo:
|
||||
case .keepMediaHeader, .keepMedia, .keepMediaInfo:
|
||||
return StorageUsageSection.keepMedia.rawValue
|
||||
case .immutableSize:
|
||||
return StorageUsageSection.immutableSize.rawValue
|
||||
case .collecting, .clearAll:
|
||||
return StorageUsageSection.all.rawValue
|
||||
case .storageHeader, .storageUsage, .collecting, .clearAll:
|
||||
return StorageUsageSection.storage.rawValue
|
||||
case .peersHeader, .peer:
|
||||
return StorageUsageSection.peers.rawValue
|
||||
}
|
||||
@ -64,27 +84,37 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .keepMedia:
|
||||
case .keepMediaHeader:
|
||||
return 0
|
||||
case .keepMediaInfo:
|
||||
case .keepMedia:
|
||||
return 1
|
||||
case .collecting:
|
||||
case .keepMediaInfo:
|
||||
return 2
|
||||
case .immutableSize:
|
||||
case .storageHeader:
|
||||
return 3
|
||||
case .clearAll:
|
||||
case .storageUsage:
|
||||
return 4
|
||||
case .peersHeader:
|
||||
case .collecting:
|
||||
return 5
|
||||
case let .peer(index, _, _, _, _, _, _, _):
|
||||
return 6 + index
|
||||
case .clearAll:
|
||||
return 6
|
||||
case .peersHeader:
|
||||
return 7
|
||||
case let .peer(index, _, _, _, _, _, _, _, _):
|
||||
return 8 + index
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: StorageUsageEntry, rhs: StorageUsageEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .keepMedia(lhsTheme, lhsText, lhsValue):
|
||||
if case let .keepMedia(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
case let .keepMediaHeader(lhsTheme, lhsText):
|
||||
if case let .keepMediaHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .keepMedia(lhsTheme, lhsStrings, lhsValue):
|
||||
if case let .keepMedia(rhsTheme, rhsStrings, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -95,20 +125,26 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .storageHeader(lhsTheme, lhsText):
|
||||
if case let .storageHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .storageUsage(lhsTheme, lhsDateTimeFormat, lhsCategories):
|
||||
if case let .storageUsage(rhsTheme, rhsDateTimeFormat, rhsCategories) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsCategories == rhsCategories {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .collecting(lhsTheme, lhsText):
|
||||
if case let .collecting(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .immutableSize(lhsTheme, lhsText, lhsValue):
|
||||
if case let .immutableSize(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .clearAll(lhsTheme, lhsText, lhsValue, lhsEnabled):
|
||||
if case let .clearAll(rhsTheme, rhsText, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
|
||||
case let .clearAll(lhsTheme, lhsText, lhsEnabled):
|
||||
if case let .clearAll(rhsTheme, rhsText, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -119,8 +155,8 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsChatPeer, lhsValue):
|
||||
if case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsChatPeer, rhsValue) = rhs {
|
||||
case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsChatPeer, lhsValue, lhsRevealed):
|
||||
if case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsChatPeer, rhsValue, rhsRevealed) = rhs {
|
||||
if lhsIndex != rhsIndex {
|
||||
return false
|
||||
}
|
||||
@ -145,6 +181,9 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
||||
if lhsValue != rhsValue {
|
||||
return false
|
||||
}
|
||||
if lhsRevealed != rhsRevealed {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -159,54 +198,62 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
||||
func item(_ arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! StorageUsageControllerArguments
|
||||
switch self {
|
||||
case let .keepMedia(theme, text, value):
|
||||
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.updateKeepMedia()
|
||||
})
|
||||
case let .keepMediaHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .keepMedia(theme, strings, value):
|
||||
return KeepMediaDurationPickerItem(theme: theme, strings: strings, value: value, sectionId: self.section, updated: { updatedValue in
|
||||
arguments.updateKeepMediaTimeout(updatedValue)
|
||||
})
|
||||
case let .keepMediaInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section)
|
||||
case let .storageHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .storageUsage(theme, dateTimeFormat, categories):
|
||||
return StorageUsageItem(theme: theme, dateTimeFormat: dateTimeFormat, categories: categories, sectionId: self.section)
|
||||
case let .collecting(theme, text):
|
||||
return CalculatingCacheSizeItem(theme: theme, title: text, sectionId: self.section, style: .blocks)
|
||||
case let .immutableSize(theme, title, value):
|
||||
return ItemListDisclosureItem(theme: theme, icon: nil, title: title, enabled: false, titleColor: .primary, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: nil)
|
||||
case let .clearAll(theme, text, enabled):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
if enabled {
|
||||
arguments.openClearAll()
|
||||
}
|
||||
})
|
||||
case let .peersHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .clearAll(theme, text, value, enabled):
|
||||
return ItemListDisclosureItem(theme: theme, icon: nil, title: text, enabled: enabled, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
arguments.openClearAll()
|
||||
})
|
||||
case let .peer(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, chatPeer, value):
|
||||
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, aliasHandling: .threatSelfAsSaved, nameColor: chatPeer == nil ? .primary : .secret, presence: nil, text: .none, label: .disclosure(value), editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
case let .peer(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, chatPeer, value, revealed):
|
||||
var options: [ItemListPeerItemRevealOption] = [ItemListPeerItemRevealOption(type: .destructive, title: strings.ClearCache_Clear, action: {
|
||||
arguments.clearPeerMedia(peer.id)
|
||||
})]
|
||||
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, aliasHandling: .threatSelfAsSaved, nameColor: chatPeer == nil ? .primary : .secret, presence: nil, text: .none, label: .disclosure(value), editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
let resolvedPeer = chatPeer ?? peer
|
||||
arguments.openPeerMedia(resolvedPeer.id)
|
||||
}, setPeerIdWithRevealedOptions: { previousId, id in
|
||||
|
||||
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||
arguments.setPeerIdWithRevealedOptions(peerId, fromPeerId)
|
||||
}, removePeer: { _ in
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func stringForKeepMediaTimeout(strings: PresentationStrings, timeout: Int32) -> String {
|
||||
if timeout > 1 * 31 * 24 * 60 * 60 {
|
||||
return strings.MessageTimer_Forever
|
||||
} else {
|
||||
return timeIntervalString(strings: strings, value: timeout)
|
||||
private struct StoragUsageState: Equatable {
|
||||
let peerIdWithRevealedOptions: PeerId?
|
||||
|
||||
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> StoragUsageState {
|
||||
return StoragUsageState(peerIdWithRevealedOptions: peerIdWithRevealedOptions)
|
||||
}
|
||||
}
|
||||
|
||||
private func storageUsageControllerEntries(presentationData: PresentationData, cacheSettings: CacheStorageSettings, cacheStats: CacheUsageStatsResult?) -> [StorageUsageEntry] {
|
||||
private func storageUsageControllerEntries(presentationData: PresentationData, cacheSettings: CacheStorageSettings, cacheStats: CacheUsageStatsResult?, state: StoragUsageState) -> [StorageUsageEntry] {
|
||||
var entries: [StorageUsageEntry] = []
|
||||
|
||||
entries.append(.keepMedia(presentationData.theme, presentationData.strings.Cache_KeepMedia, stringForKeepMediaTimeout(strings: presentationData.strings, timeout: cacheSettings.defaultCacheStorageTimeout)))
|
||||
entries.append(.keepMediaHeader(presentationData.theme, presentationData.strings.Cache_KeepMedia.uppercased()))
|
||||
entries.append(.keepMedia(presentationData.theme, presentationData.strings, cacheSettings.defaultCacheStorageTimeout))
|
||||
entries.append(.keepMediaInfo(presentationData.theme, presentationData.strings.Cache_Help))
|
||||
|
||||
var addedHeader = false
|
||||
|
||||
entries.append(.storageHeader(presentationData.theme, presentationData.strings.ClearCache_StorageTitle(stringForDeviceType().uppercased()).0))
|
||||
if let cacheStats = cacheStats, case let .result(stats) = cacheStats {
|
||||
entries.append(.immutableSize(presentationData.theme, presentationData.strings.Cache_ServiceFiles, dataSizeString(stats.immutableSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
|
||||
var peerSizes: Int64 = 0
|
||||
var statsByPeerId: [(PeerId, Int64)] = []
|
||||
var peerIndices: [PeerId: Int] = [:]
|
||||
@ -230,9 +277,27 @@ private func storageUsageControllerEntries(presentationData: PresentationData, c
|
||||
peerSizes += combinedSize
|
||||
}
|
||||
|
||||
let totalSize = Int64(peerSizes + stats.otherSize + stats.cacheSize + stats.tempSize)
|
||||
let telegramCacheSize = Int64(peerSizes + stats.otherSize + stats.cacheSize + stats.tempSize)
|
||||
let totalTelegramSize = telegramCacheSize + stats.immutableSize
|
||||
|
||||
entries.append(.clearAll(presentationData.theme, presentationData.strings.Cache_ClearCache, totalSize > 0 ? dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) : presentationData.strings.Cache_ClearEmpty, totalSize > 0))
|
||||
var categories: [StorageUsageCategory] = []
|
||||
let totalSpace = max(totalDiskSpace(), 1)
|
||||
let freeSpace = freeDiskSpace()
|
||||
let otherAppsSpace = totalSpace - freeSpace - totalTelegramSize
|
||||
|
||||
let totalSpaceValue = CGFloat(totalSpace)
|
||||
|
||||
if telegramCacheSize > 0 {
|
||||
categories.append(StorageUsageCategory(title: presentationData.strings.ClearCache_StorageCache, size: totalTelegramSize, fraction: CGFloat(totalTelegramSize) / totalSpaceValue, color: presentationData.theme.list.itemAccentColor))
|
||||
} else {
|
||||
categories.append(StorageUsageCategory(title: presentationData.strings.ClearCache_StorageServiceFiles, size: totalTelegramSize, fraction: CGFloat(totalTelegramSize) / totalSpaceValue, color: presentationData.theme.list.itemAccentColor))
|
||||
}
|
||||
categories.append(StorageUsageCategory(title: presentationData.strings.ClearCache_StorageOtherApps, size: otherAppsSpace, fraction: CGFloat(otherAppsSpace) / totalSpaceValue, color: presentationData.theme.list.itemBlocksSeparatorColor))
|
||||
categories.append(StorageUsageCategory(title: presentationData.strings.ClearCache_StorageFree, size: freeSpace, fraction: CGFloat(freeSpace) / totalSpaceValue, color: UIColor(rgb: 0xf2f1f7)))
|
||||
|
||||
entries.append(.storageUsage(presentationData.theme, presentationData.dateTimeFormat, categories))
|
||||
|
||||
entries.append(.clearAll(presentationData.theme, presentationData.strings.ClearCache_ClearCache, telegramCacheSize > 0))
|
||||
|
||||
var index: Int32 = 0
|
||||
for (peerId, size) in statsByPeerId.sorted(by: { $0.1 > $1.1 }) {
|
||||
@ -248,7 +313,7 @@ private func storageUsageControllerEntries(presentationData: PresentationData, c
|
||||
chatPeer = mainPeer
|
||||
mainPeer = associatedPeer
|
||||
}
|
||||
entries.append(.peer(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, mainPeer, chatPeer, dataSizeString(size, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.peer(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, mainPeer, chatPeer, dataSizeString(size, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator), state.peerIdWithRevealedOptions == peer.id))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
@ -273,7 +338,28 @@ private func stringForCategory(strings: PresentationStrings, category: PeerCache
|
||||
}
|
||||
}
|
||||
|
||||
public func storageUsageController(context: AccountContext, isModal: Bool = false) -> ViewController {
|
||||
func cacheUsageStats(context: AccountContext) -> Signal<CacheUsageStatsResult?, NoError> {
|
||||
let containerPath = context.sharedContext.applicationBindings.containerPath
|
||||
let additionalPaths: [String] = [
|
||||
NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0],
|
||||
containerPath + "/Documents/files",
|
||||
containerPath + "/Documents/video",
|
||||
containerPath + "/Documents/audio",
|
||||
containerPath + "/Documents/mediacache",
|
||||
containerPath + "/Documents/tempcache_v1/store",
|
||||
]
|
||||
return .single(nil)
|
||||
|> then(collectCacheUsageStats(account: context.account, additionalCachePaths: additionalPaths, logFilesPath: context.sharedContext.applicationBindings.containerPath + "/telegram-data/logs")
|
||||
|> map(Optional.init))
|
||||
}
|
||||
|
||||
public func storageUsageController(context: AccountContext, cacheUsagePromise: Promise<CacheUsageStatsResult?>? = nil, isModal: Bool = false) -> ViewController {
|
||||
let statePromise = ValuePromise(StoragUsageState(peerIdWithRevealedOptions: nil))
|
||||
let stateValue = Atomic(value: StoragUsageState(peerIdWithRevealedOptions: nil))
|
||||
let updateState: ((StoragUsageState) -> StoragUsageState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
let cacheSettingsPromise = Promise<CacheStorageSettings>()
|
||||
cacheSettingsPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings])
|
||||
|> map { sharedData -> CacheStorageSettings in
|
||||
@ -289,59 +375,27 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
|
||||
|
||||
var presentControllerImpl: ((ViewController, PresentationContextType, Any?) -> Void)?
|
||||
|
||||
let statsPromise = Promise<CacheUsageStatsResult?>()
|
||||
let resetStats: () -> Void = {
|
||||
let containerPath = context.sharedContext.applicationBindings.containerPath
|
||||
let additionalPaths: [String] = [
|
||||
NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0],
|
||||
containerPath + "/Documents/files",
|
||||
containerPath + "/Documents/video",
|
||||
containerPath + "/Documents/audio",
|
||||
containerPath + "/Documents/mediacache",
|
||||
containerPath + "/Documents/tempcache_v1/store",
|
||||
]
|
||||
statsPromise.set(.single(nil)
|
||||
|> then(collectCacheUsageStats(account: context.account, additionalCachePaths: additionalPaths, logFilesPath: context.sharedContext.applicationBindings.containerPath + "/telegram-data/logs")
|
||||
|> map(Optional.init)))
|
||||
var statsPromise: Promise<CacheUsageStatsResult?>
|
||||
if let cacheUsagePromise = cacheUsagePromise {
|
||||
statsPromise = cacheUsagePromise
|
||||
} else {
|
||||
statsPromise = Promise<CacheUsageStatsResult?>()
|
||||
statsPromise.set(cacheUsageStats(context: context))
|
||||
}
|
||||
|
||||
let resetStats: () -> Void = {
|
||||
statsPromise.set(cacheUsageStats(context: context))
|
||||
}
|
||||
resetStats()
|
||||
|
||||
let actionDisposables = DisposableSet()
|
||||
|
||||
let clearDisposable = MetaDisposable()
|
||||
actionDisposables.add(clearDisposable)
|
||||
|
||||
let arguments = StorageUsageControllerArguments(account: context.account, updateKeepMedia: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
||||
let dismissAction: () -> Void = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
let timeoutAction: (Int32) -> Void = { timeout in
|
||||
let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
return current.withUpdatedDefaultCacheStorageTimeout(timeout)
|
||||
}).start()
|
||||
}
|
||||
var values: [Int32] = [
|
||||
3 * 24 * 60 * 60,
|
||||
7 * 24 * 60 * 60,
|
||||
1 * 31 * 24 * 60 * 60,
|
||||
Int32.max
|
||||
]
|
||||
#if DEBUG
|
||||
values.insert(60 * 60, at: 0)
|
||||
#endif
|
||||
let timeoutItems: [ActionSheetItem] = values.map { value in
|
||||
return ActionSheetButtonItem(title: stringForKeepMediaTimeout(strings: presentationData.strings, timeout: value), action: {
|
||||
dismissAction()
|
||||
timeoutAction(value)
|
||||
})
|
||||
}
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: timeoutItems),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
presentControllerImpl?(controller, .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
let arguments = StorageUsageControllerArguments(account: context.account, updateKeepMediaTimeout: { value in
|
||||
let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
return current.withUpdatedDefaultCacheStorageTimeout(value)
|
||||
}).start()
|
||||
}, openClearAll: {
|
||||
let _ = (statsPromise.get()
|
||||
|> take(1)
|
||||
@ -535,8 +589,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
|
||||
clearDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
statsPromise.set(.single(.result(resultStats)))
|
||||
let deviceName = UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone"
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", deviceName).0), elevatedLayout: false, action: { _ in }), .current, nil)
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in }), .current, nil)
|
||||
}))
|
||||
}
|
||||
|
||||
@ -717,8 +770,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
|
||||
clearDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
statsPromise.set(.single(.result(resultStats)))
|
||||
let deviceName = UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone"
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", deviceName).0), elevatedLayout: false, action: { _ in }), .current, nil)
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in }), .current, nil)
|
||||
}))
|
||||
}
|
||||
|
||||
@ -734,18 +786,147 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
|
||||
}
|
||||
}
|
||||
})
|
||||
}, clearPeerMedia: { peerId in
|
||||
let _ = (statsPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak statsPromise] result in
|
||||
if let result = result, case let .result(stats) = result {
|
||||
var additionalPeerId: PeerId?
|
||||
if var categories = stats.media[peerId], let peer = stats.peers[peerId] {
|
||||
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
||||
for (_, peer) in stats.peers {
|
||||
if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference, migrationReference.peerId == peerId {
|
||||
if let additionalCategories = stats.media[group.id] {
|
||||
additionalPeerId = group.id
|
||||
categories.merge(additionalCategories, uniquingKeysWith: { lhs, rhs in
|
||||
return lhs.merging(rhs, uniquingKeysWith: { lhs, rhs in
|
||||
return lhs + rhs
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sizeIndex: [PeerCacheUsageCategory: (Bool, Int64)] = [:]
|
||||
let validCategories: [PeerCacheUsageCategory] = [.image, .video, .audio, .file]
|
||||
|
||||
var totalSize: Int64 = 0
|
||||
|
||||
for categoryId in validCategories {
|
||||
if let media = categories[categoryId] {
|
||||
var categorySize: Int64 = 0
|
||||
for (_, size) in media {
|
||||
categorySize += size
|
||||
}
|
||||
sizeIndex[categoryId] = (true, categorySize)
|
||||
totalSize += categorySize
|
||||
}
|
||||
}
|
||||
|
||||
if let statsPromise = statsPromise {
|
||||
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
|
||||
var clearMediaIds = Set<MediaId>()
|
||||
|
||||
var media = stats.media
|
||||
if var categories = media[peerId] {
|
||||
for category in clearCategories {
|
||||
if let contents = categories[category] {
|
||||
for (mediaId, _) in contents {
|
||||
clearMediaIds.insert(mediaId)
|
||||
}
|
||||
}
|
||||
categories.removeValue(forKey: category)
|
||||
}
|
||||
|
||||
media[peerId] = categories
|
||||
}
|
||||
if let additionalPeerId = additionalPeerId {
|
||||
if var categories = media[additionalPeerId] {
|
||||
for category in clearCategories {
|
||||
if let contents = categories[category] {
|
||||
for (mediaId, _) in contents {
|
||||
clearMediaIds.insert(mediaId)
|
||||
}
|
||||
}
|
||||
categories.removeValue(forKey: category)
|
||||
}
|
||||
|
||||
media[additionalPeerId] = categories
|
||||
}
|
||||
}
|
||||
|
||||
var clearResourceIds = Set<WrappedMediaResourceId>()
|
||||
for id in clearMediaIds {
|
||||
if let ids = stats.mediaResourceIds[id] {
|
||||
for resourceId in ids {
|
||||
clearResourceIds.insert(WrappedMediaResourceId(resourceId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var signal = clearCachedMediaResources(account: context.account, mediaResourceIds: clearResourceIds)
|
||||
|
||||
let resultStats = CacheUsageStats(media: media, mediaResourceIds: stats.mediaResourceIds, peers: stats.peers, otherSize: stats.otherSize, otherPaths: stats.otherPaths, cacheSize: stats.cacheSize, tempPaths: stats.tempPaths, tempSize: stats.tempSize, immutableSize: stats.immutableSize)
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
presentControllerImpl?(controller, .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.15, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.start()
|
||||
|
||||
signal = signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
cancelImpl = {
|
||||
clearDisposable.set(nil)
|
||||
resetStats()
|
||||
}
|
||||
clearDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
statsPromise.set(.single(.result(resultStats)))
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in }), .current, nil)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
updateState { state in
|
||||
return state.withUpdatedPeerIdWithRevealedOptions(nil)
|
||||
}
|
||||
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||
updateState { state in
|
||||
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
|
||||
return state.withUpdatedPeerIdWithRevealedOptions(peerId)
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData, cacheSettingsPromise.get(), statsPromise.get()) |> deliverOnMainQueue
|
||||
|> map { presentationData, cacheSettings, cacheStats -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let signal = combineLatest(context.sharedContext.presentationData, cacheSettingsPromise.get(), statsPromise.get(), statePromise.get()) |> deliverOnMainQueue
|
||||
|> map { presentationData, cacheSettings, cacheStats, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let leftNavigationButton = isModal ? ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
}) : nil
|
||||
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Cache_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: storageUsageControllerEntries(presentationData: presentationData, cacheSettings: cacheSettings, cacheStats: cacheStats), style: .blocks, emptyStateItem: nil, animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: storageUsageControllerEntries(presentationData: presentationData, cacheSettings: cacheSettings, cacheStats: cacheStats, state: state), style: .blocks, emptyStateItem: nil, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
} |> afterDisposed {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ import ItemListUI
|
||||
import PhotoResources
|
||||
import OpenInExternalAppUI
|
||||
|
||||
public class WebBrowserItem: ListViewItem, ItemListItem {
|
||||
class WebBrowserItem: ListViewItem, ItemListItem {
|
||||
let account: Account
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
@ -71,7 +71,7 @@ public class WebBrowserItem: ListViewItem, ItemListItem {
|
||||
|
||||
private let titleFont = Font.regular(17.0)
|
||||
|
||||
public class WebBrowserItemNode: ListViewItemNode {
|
||||
private final class WebBrowserItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
@ -9,10 +9,13 @@ public func fetchHttpResource(url: String) -> Signal<MediaResourceDataFetchResul
|
||||
return Signal { subscriber in
|
||||
subscriber.putNext(.reset)
|
||||
let disposable = signal.start(next: { next in
|
||||
let data = next as! Data
|
||||
let fetchResult: MediaResourceDataFetchResult = .dataPart(resourceOffset: 0, data: data, range: 0 ..< data.count, complete: true)
|
||||
subscriber.putNext(fetchResult)
|
||||
subscriber.putCompletion()
|
||||
if let response = next as? MTHttpResponse {
|
||||
let fetchResult: MediaResourceDataFetchResult = .dataPart(resourceOffset: 0, data: response.data, range: 0 ..< response.data.count, complete: true)
|
||||
subscriber.putNext(fetchResult)
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putError(.generic)
|
||||
}
|
||||
}, error: { _ in
|
||||
subscriber.putError(.generic)
|
||||
}, completed: {
|
||||
|
File diff suppressed because it is too large
Load Diff
12
submodules/TelegramStringFormatting/Sources/DeviceType.swift
Normal file
12
submodules/TelegramStringFormatting/Sources/DeviceType.swift
Normal 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"
|
||||
}
|
||||
}
|
@ -3454,8 +3454,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self, strongSelf.beginMediaRecordingRequestId == requestId else {
|
||||
return
|
||||
}
|
||||
guard checkAvailableDiskSpace(context: strongSelf.context, present: { [weak self] c, a in
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
guard checkAvailableDiskSpace(context: strongSelf.context, push: { [weak self] c in
|
||||
self?.push(c)
|
||||
}) else {
|
||||
return
|
||||
}
|
||||
@ -5292,8 +5292,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
disposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
if let strongSelf = self, let layout = strongSelf.validLayout {
|
||||
let deviceName = UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone"
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", deviceName).0), elevatedLayout: true, action: { _ in }), in: .current)
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: true, action: { _ in }), in: .current)
|
||||
}
|
||||
}))
|
||||
|
||||
@ -5457,12 +5456,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}, openCamera: { [weak self] cameraView, menuController in
|
||||
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
||||
presentedLegacyCamera(context: strongSelf.context, peer: peer, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: !strongSelf.presentationInterfaceState.isScheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, sendMessagesWithSignals: { [weak self] signals in
|
||||
presentedLegacyCamera(context: strongSelf.context, peer: peer, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: !strongSelf.presentationInterfaceState.isScheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
|
||||
if let strongSelf = self {
|
||||
if editMediaOptions != nil {
|
||||
strongSelf.editMessageMediaWithLegacySignals(signals!)
|
||||
} else {
|
||||
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: false)
|
||||
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime)
|
||||
}
|
||||
if !inputText.string.isEmpty {
|
||||
//strongSelf.clearInputText()
|
||||
|
@ -103,7 +103,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
self.contentContainerNode.addSubnode(self.cancelButton)
|
||||
self.contentContainerNode.addSubnode(self.doneButton)
|
||||
if case .scheduledMessages(true) = self.mode {
|
||||
self.contentContainerNode.addSubnode(self.onlineButton)
|
||||
//self.contentContainerNode.addSubnode(self.onlineButton)
|
||||
}
|
||||
|
||||
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||
@ -316,7 +316,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
|
||||
var buttonOffset: CGFloat = 0.0
|
||||
if case .scheduledMessages(true) = self.mode {
|
||||
buttonOffset += 60.0
|
||||
//buttonOffset += 60.0
|
||||
}
|
||||
|
||||
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||
|
@ -7,7 +7,7 @@ import AlertUI
|
||||
import PresentationDataUtils
|
||||
import SettingsUI
|
||||
|
||||
func totalDiskSpace() -> Int64 {
|
||||
private func totalDiskSpace() -> Int64 {
|
||||
do {
|
||||
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
|
||||
return (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? 0
|
||||
@ -16,7 +16,7 @@ func totalDiskSpace() -> Int64 {
|
||||
}
|
||||
}
|
||||
|
||||
func freeDiskSpace() -> Int64 {
|
||||
private func freeDiskSpace() -> Int64 {
|
||||
do {
|
||||
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
|
||||
return (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0
|
||||
@ -25,18 +25,16 @@ func freeDiskSpace() -> Int64 {
|
||||
}
|
||||
}
|
||||
|
||||
func checkAvailableDiskSpace(context: AccountContext, threshold: Int64 = 100 * 1024 * 1024, present: @escaping (ViewController, Any?) -> Void) -> Bool {
|
||||
func checkAvailableDiskSpace(context: AccountContext, threshold: Int64 = 100 * 1024 * 1024, push: @escaping (ViewController) -> Void) -> Bool {
|
||||
guard freeDiskSpace() < threshold else {
|
||||
return true
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = textAlertController(context: context, title: nil, text: presentationData.strings.Cache_LowDiskSpaceText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
let controller = storageUsageController(context: context, isModal: true)
|
||||
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
|
||||
push(storageUsageController(context: context, isModal: true))
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||
present(controller, nil)
|
||||
push(controller)
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import ShareController
|
||||
import LegacyUI
|
||||
import LegacyMediaPickerUI
|
||||
|
||||
func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, hasSchedule: Bool, sendMessagesWithSignals: @escaping ([Any]?) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void) {
|
||||
func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, hasSchedule: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void) {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
|
||||
@ -29,6 +29,12 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
|
||||
controller = TGCameraController()
|
||||
}
|
||||
|
||||
controller.presentScheduleController = { done in
|
||||
presentSchedulePicker { time in
|
||||
done?(time)
|
||||
}
|
||||
}
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
} else {
|
||||
controller.customPresentOverlayController = { [weak legacyController] generateController in
|
||||
@ -99,10 +105,10 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
|
||||
controller.finishedWithResults = { [weak menuController, weak legacyController] overlayController, selectionContext, editingContext, currentItem in
|
||||
controller.finishedWithResults = { [weak menuController, weak legacyController] overlayController, selectionContext, editingContext, currentItem, silentPosting, scheduleTime in
|
||||
if let selectionContext = selectionContext, let editingContext = editingContext {
|
||||
let signals = TGCameraController.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, storeAssets: saveCapturedPhotos && !isSecretChat, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, descriptionGenerator: legacyAssetPickerItemGenerator())
|
||||
sendMessagesWithSignals(signals)
|
||||
sendMessagesWithSignals(signals, silentPosting, scheduleTime)
|
||||
}
|
||||
|
||||
menuController?.dismiss(animated: false)
|
||||
@ -118,7 +124,7 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
|
||||
description["timer"] = timer
|
||||
}
|
||||
if let item = legacyAssetPickerItemGenerator()(description, caption, entities, nil) {
|
||||
sendMessagesWithSignals([SSignal.single(item)])
|
||||
sendMessagesWithSignals([SSignal.single(item)], false, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +149,7 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAt
|
||||
description["timer"] = timer
|
||||
}
|
||||
if let item = legacyAssetPickerItemGenerator()(description, caption, entities, nil) {
|
||||
sendMessagesWithSignals([SSignal.single(item)])
|
||||
sendMessagesWithSignals([SSignal.single(item)], false, 0)
|
||||
}
|
||||
}
|
||||
menuController?.dismiss(animated: false)
|
||||
@ -193,7 +199,7 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
|
||||
controller.finishedWithResults = { [weak controller, weak parentController, weak legacyController] overlayController, selectionContext, editingContext, currentItem in
|
||||
controller.finishedWithResults = { [weak controller, weak parentController, weak legacyController] overlayController, selectionContext, editingContext, currentItem, _, _ in
|
||||
if let selectionContext = selectionContext, let editingContext = editingContext {
|
||||
let signals = TGCameraController.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, storeAssets: saveCapturedMedia, saveEditedPhotos: saveEditedPhotos, descriptionGenerator: legacyAssetPickerItemGenerator())
|
||||
if let parentController = parentController {
|
||||
@ -218,77 +224,7 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B
|
||||
}), showInChat: nil, externalShare: false), in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
||||
//legacyController?.dismissWithAnimation()
|
||||
}
|
||||
|
||||
parentController.present(legacyController, in: .window(.root))
|
||||
|
||||
|
||||
/*TGCameraControllerWindow *controllerWindow = [[TGCameraControllerWindow alloc] initWithManager:[[TGLegacyComponentsContext shared] makeOverlayWindowManager] parentController:TGAppDelegateInstance.rootController contentController:controller];
|
||||
controllerWindow.hidden = false;
|
||||
|
||||
CGSize screenSize = TGScreenSize();
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
|
||||
controllerWindow.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
|
||||
|
||||
CGRect startFrame = CGRectMake(0, screenSize.height, screenSize.width, screenSize.height);
|
||||
[controller beginTransitionInFromRect:startFrame];
|
||||
|
||||
__weak TGCameraController *weakCameraController = controller;
|
||||
controller.finishedWithResults = ^(TGOverlayController *controller, TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem)
|
||||
{
|
||||
__autoreleasing NSString *disabledMessage = nil;
|
||||
if (![TGApplicationFeatures isPhotoUploadEnabledForPeerType:TGApplicationFeaturePeerPrivate disabledMessage:&disabledMessage])
|
||||
{
|
||||
[TGCustomAlertView presentAlertWithTitle:TGLocalized(@"FeatureDisabled.Oops") message:disabledMessage cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
__strong TGCameraController *strongCameraController = weakCameraController;
|
||||
if (strongCameraController == nil)
|
||||
return;
|
||||
|
||||
[TGCameraController showTargetController:[TGCameraController resultSignalsForSelectionContext:selectionContext editingContext:editingContext currentItem:currentItem storeAssets:false saveEditedPhotos:false descriptionGenerator:^id(id item, NSString *caption, NSArray *entities, __unused NSString *stickers)
|
||||
{
|
||||
if ([item isKindOfClass:[NSDictionary class]])
|
||||
{
|
||||
NSDictionary *dict = (NSDictionary *)item;
|
||||
NSString *type = dict[@"type"];
|
||||
|
||||
if ([type isEqualToString:@"editedPhoto"])
|
||||
{
|
||||
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
|
||||
result[@"type"] = @"image";
|
||||
result[@"image"] = dict[@"image"];
|
||||
if (caption.length > 0)
|
||||
result[@"caption"] = caption;
|
||||
if (entities.count > 0)
|
||||
result[@"entities"] = entities;
|
||||
if (dict[@"stickers"] != nil)
|
||||
result[@"stickers"] = dict[@"stickers"];
|
||||
|
||||
return result;
|
||||
}
|
||||
else if ([type isEqualToString:@"cameraVideo"])
|
||||
{
|
||||
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
|
||||
result[@"type"] = @"cameraVideo";
|
||||
result[@"url"] = dict[@"url"];
|
||||
if (dict[@"adjustments"] != nil)
|
||||
result[@"adjustments"] = dict[@"adjustments"];
|
||||
if (entities.count > 0)
|
||||
result[@"entities"] = entities;
|
||||
if (dict[@"stickers"] != nil)
|
||||
result[@"stickers"] = dict[@"stickers"];
|
||||
if (dict[@"previewImage"] != nil)
|
||||
result[@"previewImage"] = dict[@"previewImage"];
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}] cameraController:strongCameraController resultController:controller navigationController:(TGNavigationController *)controller.navigationController];
|
||||
};*/
|
||||
}
|
||||
|
Binary file not shown.
@ -79,6 +79,8 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
private var didPlayToEndTimeObserver: NSObjectProtocol?
|
||||
private var timeObserver: Any?
|
||||
|
||||
private var seekId: Int = 0
|
||||
|
||||
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, url: String, imageReference: ImageMediaReference, intrinsicDimensions: CGSize, approximateDuration: Int32) {
|
||||
self.audioSessionManager = audioSessionManager
|
||||
|
||||
@ -131,7 +133,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: strongSelf.statusValue.duration, dimensions: CGSize(), timestamp: CMTimeGetSeconds(time), baseRate: 1.0, seekId: 0, status: strongSelf.statusValue.status, soundEnabled: true)
|
||||
strongSelf.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: strongSelf.statusValue.duration, dimensions: CGSize(), timestamp: CMTimeGetSeconds(time), baseRate: 1.0, seekId: strongSelf.seekId, status: strongSelf.statusValue.status, soundEnabled: true)
|
||||
strongSelf._status.set(strongSelf.statusValue)
|
||||
}
|
||||
}
|
||||
@ -171,7 +173,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
} else {
|
||||
status = isPlaying ? .playing : .paused
|
||||
}
|
||||
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: 0, status: status, soundEnabled: true)
|
||||
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: self.seekId, status: status, soundEnabled: true)
|
||||
self._status.set(self.statusValue)
|
||||
} else if keyPath == "playbackBufferEmpty" {
|
||||
let isPlaying = !self.player.rate.isZero
|
||||
@ -182,7 +184,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
} else {
|
||||
status = isPlaying ? .playing : .paused
|
||||
}
|
||||
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: 0, status: status, soundEnabled: true)
|
||||
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: self.seekId, status: status, soundEnabled: true)
|
||||
self._status.set(self.statusValue)
|
||||
} else if keyPath == "playbackLikelyToKeepUp" || keyPath == "playbackBufferFull" {
|
||||
let isPlaying = !self.player.rate.isZero
|
||||
@ -193,7 +195,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
} else {
|
||||
status = isPlaying ? .playing : .paused
|
||||
}
|
||||
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: 0, status: status, soundEnabled: true)
|
||||
self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: self.statusValue.timestamp, baseRate: 1.0, seekId: self.seekId, status: status, soundEnabled: true)
|
||||
self._status.set(self.statusValue)
|
||||
|
||||
if !self.didBeginPlaying {
|
||||
@ -217,7 +219,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
func play() {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
if !self.initializedStatus {
|
||||
self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .buffering(initial: true, whilePlaying: true), soundEnabled: true))
|
||||
self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true), soundEnabled: true))
|
||||
}
|
||||
if !self.hasAudioSession {
|
||||
self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play, activate: { [weak self] _ in
|
||||
@ -236,7 +238,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
func pause() {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
if !self.initializedStatus {
|
||||
self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true))
|
||||
self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: self.seekId, status: .paused, soundEnabled: true))
|
||||
}
|
||||
self.player.pause()
|
||||
}
|
||||
@ -256,6 +258,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
|
||||
func seek(_ timestamp: Double) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
self.seekId += 1
|
||||
self.playerItem.seek(to: CMTimeMake(value: Int64(timestamp) * 1000, timescale: 1000))
|
||||
}
|
||||
|
||||
|
Binary file not shown.
@ -187,270 +187,273 @@ public final class WalletStrings: Equatable {
|
||||
private let _s: [Int: String]
|
||||
private let _r: [Int: [(Int, NSRange)]]
|
||||
private let _ps: [Int: String]
|
||||
public var Wallet_Updated_JustNow: String { return self._s[0]! }
|
||||
public var Wallet_WordCheck_IncorrectText: String { return self._s[1]! }
|
||||
public var Wallet_Month_ShortNovember: String { return self._s[2]! }
|
||||
public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[3]! }
|
||||
public var Wallet_Info_Send: String { return self._s[4]! }
|
||||
public var Wallet_TransactionInfo_SendGrams: String { return self._s[5]! }
|
||||
public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[6]!, self._r[6]!, [_0])
|
||||
public var Wallet_Created_ExportErrorText: String { return self._s[0]! }
|
||||
public var Wallet_Send_ConfirmationConfirm: String { return self._s[2]! }
|
||||
public var Wallet_Month_GenJuly: String { return self._s[3]! }
|
||||
public var Wallet_Month_GenDecember: String { return self._s[4]! }
|
||||
public var Wallet_Month_ShortJanuary: String { return self._s[5]! }
|
||||
public var Wallet_WordCheck_Title: String { return self._s[6]! }
|
||||
public var Wallet_Month_ShortMarch: String { return self._s[7]! }
|
||||
public var Wallet_Month_GenSeptember: String { return self._s[8]! }
|
||||
public var Wallet_Info_Address: String { return self._s[9]! }
|
||||
public var Wallet_RestoreFailed_CreateWallet: String { return self._s[10]! }
|
||||
public var Wallet_Intro_NotNow: String { return self._s[11]! }
|
||||
public var Wallet_AccessDenied_Camera: String { return self._s[12]! }
|
||||
public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[13]! }
|
||||
public var Wallet_Receive_ShareAddress: String { return self._s[14]! }
|
||||
public var Wallet_Sent_ViewWallet: String { return self._s[15]! }
|
||||
public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[16]! }
|
||||
public var Wallet_Completed_Text: String { return self._s[17]! }
|
||||
public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[18]!, self._r[18]!, [_0])
|
||||
}
|
||||
public var Wallet_Sent_Title: String { return self._s[7]! }
|
||||
public var Wallet_Receive_ShareUrlInfo: String { return self._s[8]! }
|
||||
public var Wallet_RestoreFailed_Title: String { return self._s[9]! }
|
||||
public var Wallet_TransactionInfo_CopyAddress: String { return self._s[11]! }
|
||||
public var Wallet_Settings_BackupWallet: String { return self._s[12]! }
|
||||
public var Wallet_Send_NetworkErrorTitle: String { return self._s[13]! }
|
||||
public var Wallet_Month_ShortJune: String { return self._s[14]! }
|
||||
public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[15]! }
|
||||
public var Wallet_Created_Title: String { return self._s[16]! }
|
||||
public func Wallet_Configuration_ApplyErrorTextURLUnreachable(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[17]!, self._r[17]!, [_0])
|
||||
public var Wallet_Receive_AmountText: String { return self._s[19]! }
|
||||
public var Wallet_TransactionInfo_CommentHeader: String { return self._s[20]! }
|
||||
public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[21]!, self._r[21]!, [_0])
|
||||
}
|
||||
public var Wallet_Send_SyncInProgress: String { return self._s[18]! }
|
||||
public var Wallet_Info_YourBalance: String { return self._s[19]! }
|
||||
public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[20]! }
|
||||
public var Wallet_TransactionInfo_CommentHeader: String { return self._s[21]! }
|
||||
public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[22]! }
|
||||
public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[23]!, self._r[23]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Settings_ConfigurationInfo: String { return self._s[24]! }
|
||||
public var Wallet_WordImport_IncorrectText: String { return self._s[25]! }
|
||||
public var Wallet_Month_GenJanuary: String { return self._s[26]! }
|
||||
public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[27]! }
|
||||
public var Wallet_Receive_ShareAddress: String { return self._s[28]! }
|
||||
public var Wallet_WordImport_Title: String { return self._s[29]! }
|
||||
public var Wallet_TransactionInfo_Title: String { return self._s[30]! }
|
||||
public var Wallet_Words_NotDoneText: String { return self._s[32]! }
|
||||
public var Wallet_RestoreFailed_EnterWords: String { return self._s[33]! }
|
||||
public var Wallet_WordImport_Text: String { return self._s[34]! }
|
||||
public var Wallet_RestoreFailed_Text: String { return self._s[36]! }
|
||||
public var Wallet_TransactionInfo_NoAddress: String { return self._s[37]! }
|
||||
public var Wallet_Navigation_Back: String { return self._s[38]! }
|
||||
public var Wallet_Intro_Terms: String { return self._s[39]! }
|
||||
public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[22]! }
|
||||
public var Wallet_SecureStorageReset_Title: String { return self._s[23]! }
|
||||
public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[24]! }
|
||||
public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[25]! }
|
||||
public var Wallet_Month_GenApril: String { return self._s[26]! }
|
||||
public var Wallet_AccessDenied_Settings: String { return self._s[27]! }
|
||||
public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[28]! }
|
||||
public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[29]! }
|
||||
public var Wallet_Qr_Title: String { return self._s[30]! }
|
||||
public var Wallet_Intro_ImportExisting: String { return self._s[31]! }
|
||||
public var Wallet_Send_OwnAddressAlertText: String { return self._s[32]! }
|
||||
public var Wallet_Month_GenAugust: String { return self._s[33]! }
|
||||
public var Wallet_Month_ShortDecember: String { return self._s[34]! }
|
||||
public var Wallet_Info_Receive: String { return self._s[35]! }
|
||||
public var Wallet_Send_Send: String { return self._s[36]! }
|
||||
public var Wallet_RestoreFailed_Text: String { return self._s[37]! }
|
||||
public var Wallet_Navigation_Cancel: String { return self._s[38]! }
|
||||
public var Wallet_CreateInvoice_Title: String { return self._s[39]! }
|
||||
public var Wallet_Sent_Title: String { return self._s[40]! }
|
||||
public var Wallet_WordCheck_Continue: String { return self._s[41]! }
|
||||
public var Wallet_Send_SyncInProgress: String { return self._s[43]! }
|
||||
public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[40]!, self._r[40]!, [_0])
|
||||
return formatWithArgumentRanges(self._s[44]!, self._r[44]!, [_0])
|
||||
}
|
||||
public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[41]!, self._r[41]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_TransactionInfo_AddressCopied: String { return self._s[42]! }
|
||||
public var Wallet_Month_GenMay: String { return self._s[45]! }
|
||||
public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[46]! }
|
||||
public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[47]! }
|
||||
public var Wallet_Receive_AddressHeader: String { return self._s[48]! }
|
||||
public var Wallet_Settings_BackupWallet: String { return self._s[50]! }
|
||||
public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[51]! }
|
||||
public var Wallet_Info_Send: String { return self._s[52]! }
|
||||
public var Wallet_Intro_Title: String { return self._s[53]! }
|
||||
public var Wallet_Receive_Title: String { return self._s[54]! }
|
||||
public var Wallet_Configuration_SourceHeader: String { return self._s[55]! }
|
||||
public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[56]! }
|
||||
public var Wallet_Alert_OK: String { return self._s[57]! }
|
||||
public var Wallet_Send_NetworkErrorText: String { return self._s[58]! }
|
||||
public var Wallet_Receive_CommentInfo: String { return self._s[59]! }
|
||||
public var Wallet_TransactionInfo_Title: String { return self._s[60]! }
|
||||
public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[61]! }
|
||||
public var Wallet_Completed_Title: String { return self._s[62]! }
|
||||
public var Wallet_Info_YourBalance: String { return self._s[63]! }
|
||||
public var Wallet_Configuration_Title: String { return self._s[64]! }
|
||||
public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[43]!, self._r[43]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[65]!, self._r[65]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Send_NetworkErrorText: String { return self._s[44]! }
|
||||
public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[45]! }
|
||||
public var Wallet_Intro_ImportExisting: String { return self._s[46]! }
|
||||
public var Wallet_Receive_CommentInfo: String { return self._s[47]! }
|
||||
public var Wallet_WordCheck_Continue: String { return self._s[48]! }
|
||||
public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[49]! }
|
||||
public var Wallet_Completed_Text: String { return self._s[50]! }
|
||||
public var Wallet_WordCheck_IncorrectHeader: String { return self._s[52]! }
|
||||
public var Wallet_Configuration_SourceHeader: String { return self._s[53]! }
|
||||
public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[54]! }
|
||||
public var Wallet_Receive_Title: String { return self._s[55]! }
|
||||
public var Wallet_Info_WalletCreated: String { return self._s[56]! }
|
||||
public var Wallet_Navigation_Cancel: String { return self._s[57]! }
|
||||
public var Wallet_CreateInvoice_Title: String { return self._s[58]! }
|
||||
public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[59]!, self._r[59]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_TransactionInfo_SenderHeader: String { return self._s[60]! }
|
||||
public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[61]!, self._r[61]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Month_GenAugust: String { return self._s[62]! }
|
||||
public var Wallet_Info_UnknownTransaction: String { return self._s[63]! }
|
||||
public var Wallet_Receive_CreateInvoice: String { return self._s[64]! }
|
||||
public var Wallet_Month_GenSeptember: String { return self._s[65]! }
|
||||
public var Wallet_Month_GenJuly: String { return self._s[66]! }
|
||||
public var Wallet_Receive_AddressHeader: String { return self._s[67]! }
|
||||
public var Wallet_Send_AmountText: String { return self._s[68]! }
|
||||
public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[69]! }
|
||||
public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[70]!, self._r[70]!, [_1, _2, _3])
|
||||
}
|
||||
public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[71]!, self._r[71]!, [_0])
|
||||
}
|
||||
public var Wallet_Configuration_Title: String { return self._s[73]! }
|
||||
public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[74]! }
|
||||
public var Wallet_Words_Title: String { return self._s[75]! }
|
||||
public var Wallet_Month_ShortMay: String { return self._s[76]! }
|
||||
public var Wallet_WordCheck_Title: String { return self._s[77]! }
|
||||
public var Wallet_Words_NotDoneResponse: String { return self._s[78]! }
|
||||
public var Wallet_Configuration_SourceURL: String { return self._s[79]! }
|
||||
public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[80]! }
|
||||
public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[81]! }
|
||||
public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[82]!, self._r[82]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Info_Address: String { return self._s[83]! }
|
||||
public var Wallet_Intro_CreateWallet: String { return self._s[84]! }
|
||||
public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[85]! }
|
||||
public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[66]!, self._r[66]!, [_0])
|
||||
}
|
||||
public var Wallet_Month_ShortJune: String { return self._s[67]! }
|
||||
public var Wallet_ContextMenuCopy: String { return self._s[68]! }
|
||||
public var Wallet_WordCheck_ViewWords: String { return self._s[69]! }
|
||||
public var Wallet_Send_Title: String { return self._s[70]! }
|
||||
public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[71]! }
|
||||
public var Wallet_WordImport_IncorrectText: String { return self._s[72]! }
|
||||
public var Wallet_Weekday_Yesterday: String { return self._s[73]! }
|
||||
public var Wallet_Send_AddressInfo: String { return self._s[74]! }
|
||||
public var Wallet_UnknownError: String { return self._s[75]! }
|
||||
public var Wallet_Receive_CopyAddress: String { return self._s[76]! }
|
||||
public var Wallet_Month_ShortFebruary: String { return self._s[77]! }
|
||||
public var Wallet_Intro_CreateWallet: String { return self._s[78]! }
|
||||
public var Wallet_Created_ExportErrorTitle: String { return self._s[80]! }
|
||||
public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[81]! }
|
||||
public var Wallet_Info_TransactionFrom: String { return self._s[82]! }
|
||||
public var Wallet_Month_ShortNovember: String { return self._s[83]! }
|
||||
public var Wallet_Month_ShortSeptember: String { return self._s[84]! }
|
||||
public func Wallet_Configuration_ApplyErrorTextURLUnreachable(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[85]!, self._r[85]!, [_0])
|
||||
}
|
||||
public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[86]!, self._r[86]!, [_0])
|
||||
}
|
||||
public var Wallet_Send_SendAnyway: String { return self._s[87]! }
|
||||
public var Wallet_UnknownError: String { return self._s[88]! }
|
||||
public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[89]! }
|
||||
public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[90]! }
|
||||
public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[92]! }
|
||||
public var Wallet_Configuration_SourceInfo: String { return self._s[93]! }
|
||||
public var Wallet_Words_NotDoneOk: String { return self._s[94]! }
|
||||
public var Wallet_Intro_Title: String { return self._s[95]! }
|
||||
public var Wallet_Info_Receive: String { return self._s[96]! }
|
||||
public var Wallet_Completed_ViewWallet: String { return self._s[97]! }
|
||||
public var Wallet_Month_ShortJuly: String { return self._s[98]! }
|
||||
public var Wallet_Month_ShortApril: String { return self._s[99]! }
|
||||
public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[100]!, self._r[100]!, [_1, _2])
|
||||
public var Wallet_Send_AmountText: String { return self._s[87]! }
|
||||
public var Wallet_Info_TransactionTo: String { return self._s[88]! }
|
||||
public var Wallet_Words_Done: String { return self._s[89]! }
|
||||
public var Wallet_Created_Text: String { return self._s[90]! }
|
||||
public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[91]! }
|
||||
public var Wallet_Month_ShortJuly: String { return self._s[92]! }
|
||||
public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[93]!, self._r[93]!, [_0])
|
||||
}
|
||||
public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[101]! }
|
||||
public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
public var Wallet_Created_Title: String { return self._s[94]! }
|
||||
public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[95]!, self._r[95]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Info_RefreshErrorTitle: String { return self._s[96]! }
|
||||
public var Wallet_Info_ReceiveGrams: String { return self._s[97]! }
|
||||
public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[98]! }
|
||||
public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[99]! }
|
||||
public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[100]! }
|
||||
public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[101]! }
|
||||
public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[102]!, self._r[102]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[103]! }
|
||||
public var Wallet_Send_UninitializedText: String { return self._s[104]! }
|
||||
public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[105]!, self._r[105]!, [_0])
|
||||
}
|
||||
public var Wallet_Month_GenNovember: String { return self._s[106]! }
|
||||
public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[107]!, self._r[107]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Month_GenApril: String { return self._s[108]! }
|
||||
public var Wallet_Month_ShortMarch: String { return self._s[109]! }
|
||||
public var Wallet_Month_GenFebruary: String { return self._s[110]! }
|
||||
public var Wallet_Qr_ScanCode: String { return self._s[111]! }
|
||||
public var Wallet_Receive_AddressCopied: String { return self._s[112]! }
|
||||
public var Wallet_Send_UninitializedTitle: String { return self._s[113]! }
|
||||
public var Wallet_Send_Send: String { return self._s[114]! }
|
||||
public var Wallet_Info_RefreshErrorTitle: String { return self._s[115]! }
|
||||
public var Wallet_Month_GenJune: String { return self._s[116]! }
|
||||
public var Wallet_Send_AddressHeader: String { return self._s[117]! }
|
||||
public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[118]! }
|
||||
public var Wallet_Send_Confirmation: String { return self._s[119]! }
|
||||
public var Wallet_Completed_Title: String { return self._s[120]! }
|
||||
public var Wallet_Alert_OK: String { return self._s[121]! }
|
||||
public var Wallet_Settings_DeleteWallet: String { return self._s[122]! }
|
||||
public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[123]! }
|
||||
public var Wallet_Month_ShortSeptember: String { return self._s[124]! }
|
||||
public var Wallet_Info_TransactionTo: String { return self._s[125]! }
|
||||
public var Wallet_Send_ConfirmationConfirm: String { return self._s[126]! }
|
||||
public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[127]! }
|
||||
public var Wallet_Receive_AmountText: String { return self._s[128]! }
|
||||
public var Wallet_Receive_CopyAddress: String { return self._s[129]! }
|
||||
public var Wallet_Intro_Text: String { return self._s[131]! }
|
||||
public var Wallet_Configuration_Apply: String { return self._s[132]! }
|
||||
public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[133]!, self._r[133]!, [_0])
|
||||
}
|
||||
public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[134]!, self._r[134]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_RestoreFailed_CreateWallet: String { return self._s[135]! }
|
||||
public var Wallet_Weekday_Yesterday: String { return self._s[136]! }
|
||||
public var Wallet_Receive_AmountHeader: String { return self._s[137]! }
|
||||
public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[138]! }
|
||||
public var Wallet_Month_ShortFebruary: String { return self._s[139]! }
|
||||
public var Wallet_Configuration_SourceJSON: String { return self._s[140]! }
|
||||
public var Wallet_Alert_Cancel: String { return self._s[141]! }
|
||||
public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[142]! }
|
||||
public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[143]! }
|
||||
public var Wallet_Info_TransactionFrom: String { return self._s[144]! }
|
||||
public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[145]! }
|
||||
public var Wallet_Send_OwnAddressAlertText: String { return self._s[146]! }
|
||||
public var Wallet_Words_NotDoneTitle: String { return self._s[147]! }
|
||||
public var Wallet_Month_ShortOctober: String { return self._s[148]! }
|
||||
public var Wallet_Month_GenMay: String { return self._s[149]! }
|
||||
public var Wallet_Intro_CreateErrorTitle: String { return self._s[150]! }
|
||||
public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[151]! }
|
||||
public var Wallet_Month_ShortJanuary: String { return self._s[152]! }
|
||||
public var Wallet_Month_GenMarch: String { return self._s[153]! }
|
||||
public var Wallet_Sending_Text: String { return self._s[154]! }
|
||||
public var Wallet_Month_GenOctober: String { return self._s[155]! }
|
||||
public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[156]! }
|
||||
public var Wallet_ContextMenuCopy: String { return self._s[157]! }
|
||||
public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[158]!, self._r[158]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Info_Updating: String { return self._s[160]! }
|
||||
public var Wallet_Created_ExportErrorTitle: String { return self._s[161]! }
|
||||
public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[162]! }
|
||||
public var Wallet_Sending_Title: String { return self._s[163]! }
|
||||
public var Wallet_Navigation_Done: String { return self._s[164]! }
|
||||
public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[165]! }
|
||||
public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[166]! }
|
||||
public var Wallet_Settings_Title: String { return self._s[167]! }
|
||||
public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[168]!, self._r[168]!, [_0])
|
||||
}
|
||||
public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[169]! }
|
||||
public var Wallet_Weekday_Today: String { return self._s[171]! }
|
||||
public var Wallet_Month_ShortDecember: String { return self._s[172]! }
|
||||
public var Wallet_Words_Text: String { return self._s[173]! }
|
||||
public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[174]! }
|
||||
public var Wallet_WordCheck_ViewWords: String { return self._s[175]! }
|
||||
public var Wallet_Send_AddressInfo: String { return self._s[176]! }
|
||||
public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[177]!, self._r[177]!, [_0])
|
||||
}
|
||||
public var Wallet_Intro_NotNow: String { return self._s[178]! }
|
||||
public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[179]! }
|
||||
public var Wallet_Navigation_Close: String { return self._s[180]! }
|
||||
public var Wallet_Month_GenDecember: String { return self._s[182]! }
|
||||
public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[183]! }
|
||||
public var Wallet_WordImport_IncorrectTitle: String { return self._s[184]! }
|
||||
public var Wallet_Send_AddressText: String { return self._s[185]! }
|
||||
public var Wallet_Receive_AmountInfo: String { return self._s[186]! }
|
||||
public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[187]!, self._r[187]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Month_ShortAugust: String { return self._s[188]! }
|
||||
public var Wallet_Qr_Title: String { return self._s[189]! }
|
||||
public var Wallet_Settings_Configuration: String { return self._s[190]! }
|
||||
public var Wallet_WordCheck_TryAgain: String { return self._s[191]! }
|
||||
public var Wallet_Info_TransactionPendingHeader: String { return self._s[192]! }
|
||||
public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[193]! }
|
||||
public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[194]! }
|
||||
public var Wallet_Send_TransactionInProgress: String { return self._s[195]! }
|
||||
public var Wallet_Created_Text: String { return self._s[196]! }
|
||||
public var Wallet_Created_Proceed: String { return self._s[197]! }
|
||||
public var Wallet_Words_Done: String { return self._s[198]! }
|
||||
public var Wallet_WordImport_Continue: String { return self._s[199]! }
|
||||
public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[200]! }
|
||||
public var Wallet_WordImport_CanNotRemember: String { return self._s[201]! }
|
||||
public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[202]!, self._r[202]!, [_1, _2, _3])
|
||||
}
|
||||
public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[203]!, self._r[203]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Created_ExportErrorText: String { return self._s[205]! }
|
||||
public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[206]!, self._r[206]!, [_0])
|
||||
}
|
||||
public var Wallet_Settings_DeleteWalletInfo: String { return self._s[207]! }
|
||||
public var Wallet_Intro_CreateErrorText: String { return self._s[208]! }
|
||||
public var Wallet_Sent_ViewWallet: String { return self._s[209]! }
|
||||
public var Wallet_Send_ErrorInvalidAddress: String { return self._s[210]! }
|
||||
public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[211]! }
|
||||
public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[105]! }
|
||||
public var Wallet_Send_ErrorInvalidAddress: String { return self._s[106]! }
|
||||
public var Wallet_Words_NotDoneTitle: String { return self._s[107]! }
|
||||
public var Wallet_Navigation_Back: String { return self._s[108]! }
|
||||
public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[212]!, self._r[212]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[109]!, self._r[109]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Send_Title: String { return self._s[213]! }
|
||||
public var Wallet_Info_RefreshErrorText: String { return self._s[214]! }
|
||||
public var Wallet_SecureStorageReset_Title: String { return self._s[215]! }
|
||||
public var Wallet_Receive_CommentHeader: String { return self._s[216]! }
|
||||
public var Wallet_Info_ReceiveGrams: String { return self._s[217]! }
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
public var Wallet_Info_UnknownTransaction: String { return self._s[110]! }
|
||||
public var Wallet_RestoreFailed_Title: String { return self._s[111]! }
|
||||
public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[112]!, self._r[112]!, [_0])
|
||||
}
|
||||
public var Wallet_TransactionInfo_CopyAddress: String { return self._s[113]! }
|
||||
public var Wallet_Navigation_Done: String { return self._s[114]! }
|
||||
public var Wallet_Send_UninitializedTitle: String { return self._s[115]! }
|
||||
public var Wallet_Send_AddressHeader: String { return self._s[117]! }
|
||||
public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[118]!, self._r[118]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Alert_Cancel: String { return self._s[119]! }
|
||||
public var Wallet_Send_NetworkErrorTitle: String { return self._s[120]! }
|
||||
public var Wallet_Configuration_SourceInfo: String { return self._s[121]! }
|
||||
public var Wallet_Month_ShortAugust: String { return self._s[122]! }
|
||||
public var Wallet_Words_NotDoneResponse: String { return self._s[123]! }
|
||||
public var Wallet_WordCheck_TryAgain: String { return self._s[124]! }
|
||||
public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[125]!, self._r[125]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Words_Text: String { return self._s[126]! }
|
||||
public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[127]!, self._r[127]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Month_ShortOctober: String { return self._s[128]! }
|
||||
public var Wallet_Created_Proceed: String { return self._s[129]! }
|
||||
public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[130]!, self._r[130]!, [_0])
|
||||
}
|
||||
public var Wallet_Month_ShortApril: String { return self._s[131]! }
|
||||
public var Wallet_Navigation_Close: String { return self._s[132]! }
|
||||
public var Wallet_WordCheck_IncorrectHeader: String { return self._s[133]! }
|
||||
public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[134]! }
|
||||
public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[135]!, self._r[135]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Send_AddressText: String { return self._s[137]! }
|
||||
public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[139]!, self._r[139]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Month_ShortMay: String { return self._s[140]! }
|
||||
public var Wallet_Intro_CreateErrorText: String { return self._s[142]! }
|
||||
public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[143]! }
|
||||
public var Wallet_Intro_Text: String { return self._s[144]! }
|
||||
public var Wallet_Month_GenJune: String { return self._s[145]! }
|
||||
public var Wallet_Receive_ShareUrlInfo: String { return self._s[146]! }
|
||||
public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[147]!, self._r[147]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_AccessDenied_Title: String { return self._s[149]! }
|
||||
public var Wallet_Send_SendAnyway: String { return self._s[150]! }
|
||||
public var Wallet_Configuration_SourceJSON: String { return self._s[151]! }
|
||||
public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[152]! }
|
||||
public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[153]!, self._r[153]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_TransactionInfo_SendGrams: String { return self._s[154]! }
|
||||
public var Wallet_Words_NotDoneText: String { return self._s[155]! }
|
||||
public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[156]! }
|
||||
public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[158]! }
|
||||
public var Wallet_Qr_ScanCode: String { return self._s[159]! }
|
||||
public var Wallet_WordImport_Title: String { return self._s[160]! }
|
||||
public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[161]!, self._r[161]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Intro_Terms: String { return self._s[162]! }
|
||||
public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[163]! }
|
||||
public var Wallet_Receive_CreateInvoice: String { return self._s[164]! }
|
||||
public var Wallet_Send_Confirmation: String { return self._s[165]! }
|
||||
public var Wallet_Month_GenNovember: String { return self._s[166]! }
|
||||
public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[167]!, self._r[167]!, [_1, _2])
|
||||
}
|
||||
public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[168]!, self._r[168]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_WordCheck_IncorrectText: String { return self._s[170]! }
|
||||
public var Wallet_Completed_ViewWallet: String { return self._s[171]! }
|
||||
public var Wallet_WordImport_Text: String { return self._s[172]! }
|
||||
public var Wallet_Words_NotDoneOk: String { return self._s[173]! }
|
||||
public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[174]! }
|
||||
public var Wallet_TransactionInfo_SenderHeader: String { return self._s[175]! }
|
||||
public var Wallet_Info_WalletCreated: String { return self._s[176]! }
|
||||
public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[177]! }
|
||||
public var Wallet_Sending_Text: String { return self._s[178]! }
|
||||
public var Wallet_Words_Title: String { return self._s[179]! }
|
||||
public var Wallet_Receive_CommentHeader: String { return self._s[180]! }
|
||||
public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[181]! }
|
||||
public var Wallet_Intro_CreateErrorTitle: String { return self._s[182]! }
|
||||
public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[183]!, self._r[183]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[184]! }
|
||||
public var Wallet_Configuration_Apply: String { return self._s[185]! }
|
||||
public var Wallet_WordImport_IncorrectTitle: String { return self._s[186]! }
|
||||
public var Wallet_Settings_Configuration: String { return self._s[187]! }
|
||||
public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[189]! }
|
||||
public var Wallet_Month_GenJanuary: String { return self._s[190]! }
|
||||
public var Wallet_Settings_DeleteWallet: String { return self._s[191]! }
|
||||
public var Wallet_Month_GenOctober: String { return self._s[192]! }
|
||||
public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[193]! }
|
||||
public var Wallet_Receive_AddressCopied: String { return self._s[194]! }
|
||||
public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[195]! }
|
||||
public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[196]!, self._r[196]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Receive_AmountInfo: String { return self._s[197]! }
|
||||
public var Wallet_Info_TransactionPendingHeader: String { return self._s[198]! }
|
||||
public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[199]! }
|
||||
public var Wallet_Updated_JustNow: String { return self._s[200]! }
|
||||
public var Wallet_Info_RefreshErrorText: String { return self._s[201]! }
|
||||
public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[202]! }
|
||||
public var Wallet_Settings_DeleteWalletInfo: String { return self._s[203]! }
|
||||
public var Wallet_RestoreFailed_EnterWords: String { return self._s[204]! }
|
||||
public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[205]! }
|
||||
public var Wallet_Send_TransactionInProgress: String { return self._s[206]! }
|
||||
public var Wallet_Month_GenMarch: String { return self._s[207]! }
|
||||
public var Wallet_Receive_AmountHeader: String { return self._s[208]! }
|
||||
public var Wallet_TransactionInfo_NoAddress: String { return self._s[209]! }
|
||||
public var Wallet_Weekday_Today: String { return self._s[210]! }
|
||||
public var Wallet_Configuration_SourceURL: String { return self._s[211]! }
|
||||
public var Wallet_TransactionInfo_AddressCopied: String { return self._s[212]! }
|
||||
public var Wallet_WordImport_Continue: String { return self._s[213]! }
|
||||
public var Wallet_Month_GenFebruary: String { return self._s[214]! }
|
||||
public var Wallet_Settings_Title: String { return self._s[215]! }
|
||||
public var Wallet_Info_Updating: String { return self._s[216]! }
|
||||
public var Wallet_Settings_ConfigurationInfo: String { return self._s[217]! }
|
||||
public var Wallet_WordImport_CanNotRemember: String { return self._s[218]! }
|
||||
public var Wallet_Sending_Title: String { return self._s[219]! }
|
||||
public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[220]!, self._r[220]!, [_0])
|
||||
}
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
}
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
|
@ -2731,14 +2731,12 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if node.isLast {
|
||||
if done {
|
||||
action()
|
||||
if let index = strongSelf.inputNodes.firstIndex(where: { $0 === node }) {
|
||||
if index == strongSelf.inputNodes.count - 1 {
|
||||
if done {
|
||||
action()
|
||||
}
|
||||
} else {
|
||||
strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -20.0), animated: true)
|
||||
}
|
||||
} else {
|
||||
if let index = strongSelf.inputNodes.firstIndex(where: { $0 === node }), index != strongSelf.inputNodes.count - 1 {
|
||||
strongSelf.inputNodes[index + 1].focus()
|
||||
}
|
||||
}
|
||||
@ -2749,7 +2747,9 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
|
||||
return
|
||||
}
|
||||
if node.isLast {
|
||||
strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -10.0), animated: true)
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -20.0), animated: false)
|
||||
})
|
||||
} else {
|
||||
strongSelf.scrollNode.view.scrollRectToVisible(node.frame.insetBy(dx: 0.0, dy: -10.0), animated: true)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user