Merge commit 'c59bbe4a89775305b1575c1ec63203c62f8db575'

# Conflicts:
#	TelegramUI/FetchMediaUtils.swift
This commit is contained in:
Peter 2018-10-19 20:33:48 +03:00
commit 66beeb210f
31 changed files with 2147 additions and 61 deletions

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "SettingsWatchIcon@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "SettingsWatchIcon@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 B

View File

@ -23,6 +23,12 @@
0941A9A4210B0E2E00EBE194 /* OpenInAppIconResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0941A9A3210B0E2E00EBE194 /* OpenInAppIconResources.swift */; }; 0941A9A4210B0E2E00EBE194 /* OpenInAppIconResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0941A9A3210B0E2E00EBE194 /* OpenInAppIconResources.swift */; };
0941A9A6210B822D00EBE194 /* OpenInOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0941A9A5210B822D00EBE194 /* OpenInOptions.swift */; }; 0941A9A6210B822D00EBE194 /* OpenInOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0941A9A5210B822D00EBE194 /* OpenInOptions.swift */; };
0952D1752176DEB500194860 /* NotificationMuteSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0952D1742176DEB500194860 /* NotificationMuteSettingsController.swift */; }; 0952D1752176DEB500194860 /* NotificationMuteSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0952D1742176DEB500194860 /* NotificationMuteSettingsController.swift */; };
0952D1772177FB5400194860 /* WatchPresetSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0952D1762177FB5400194860 /* WatchPresetSettings.swift */; };
096C98BA21787A5C00C211FF /* LegacyBridgeAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */; };
096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */; };
096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; };
096C98C121787C6700C211FF /* TGBridgeAudioDecoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BD21787C6700C211FF /* TGBridgeAudioDecoder.h */; };
096C98C221787C6700C211FF /* TGBridgeAudioDecoder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BE21787C6700C211FF /* TGBridgeAudioDecoder.mm */; };
09797873210633CD0077D77F /* InstantPageSettingsButtonItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09797872210633CD0077D77F /* InstantPageSettingsButtonItemNode.swift */; }; 09797873210633CD0077D77F /* InstantPageSettingsButtonItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09797872210633CD0077D77F /* InstantPageSettingsButtonItemNode.swift */; };
0979787C210642CB0077D77F /* WebEmbedPlayerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0979787B210642CB0077D77F /* WebEmbedPlayerNode.swift */; }; 0979787C210642CB0077D77F /* WebEmbedPlayerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0979787B210642CB0077D77F /* WebEmbedPlayerNode.swift */; };
0979787E210646C00077D77F /* YoutubeEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0979787D210646C00077D77F /* YoutubeEmbedImplementation.swift */; }; 0979787E210646C00077D77F /* YoutubeEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0979787D210646C00077D77F /* YoutubeEmbedImplementation.swift */; };
@ -40,6 +46,8 @@
09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */; }; 09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */; };
09C3466D2167D63A00B76780 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C3466C2167D63A00B76780 /* Accessibility.swift */; }; 09C3466D2167D63A00B76780 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C3466C2167D63A00B76780 /* Accessibility.swift */; };
09C500242142BA6400EF253E /* ItemListWebsiteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */; }; 09C500242142BA6400EF253E /* ItemListWebsiteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */; };
09D304152173C0E900C00567 /* WatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D304142173C0E900C00567 /* WatchManager.swift */; };
09D304182173C15700C00567 /* WatchSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D304172173C15700C00567 /* WatchSettingsController.swift */; };
09FE756D2153F5F900A3120F /* CallRouteActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */; }; 09FE756D2153F5F900A3120F /* CallRouteActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */; };
D0068FA821760FA300D1B315 /* StoreDownloadedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0068FA721760FA300D1B315 /* StoreDownloadedMedia.swift */; }; D0068FA821760FA300D1B315 /* StoreDownloadedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0068FA721760FA300D1B315 /* StoreDownloadedMedia.swift */; };
D007019C2029E8F2006B9E34 /* LegqacyICloudFileController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007019B2029E8F2006B9E34 /* LegqacyICloudFileController.swift */; }; D007019C2029E8F2006B9E34 /* LegqacyICloudFileController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007019B2029E8F2006B9E34 /* LegqacyICloudFileController.swift */; };
@ -111,6 +119,8 @@
D02660941F34CE5C000E2DC5 /* LegacyLocationVenueIconDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02660931F34CE5C000E2DC5 /* LegacyLocationVenueIconDataSource.swift */; }; D02660941F34CE5C000E2DC5 /* LegacyLocationVenueIconDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02660931F34CE5C000E2DC5 /* LegacyLocationVenueIconDataSource.swift */; };
D02B2B9820810DA00062476B /* StickerPaneSearchStickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02B2B9720810DA00062476B /* StickerPaneSearchStickerItem.swift */; }; D02B2B9820810DA00062476B /* StickerPaneSearchStickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02B2B9720810DA00062476B /* StickerPaneSearchStickerItem.swift */; };
D02B676320800A00001A864A /* StickerPaneSearchBarPlaceholderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02B676220800A00001A864A /* StickerPaneSearchBarPlaceholderItem.swift */; }; D02B676320800A00001A864A /* StickerPaneSearchBarPlaceholderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02B676220800A00001A864A /* StickerPaneSearchBarPlaceholderItem.swift */; };
D02C81712177729000CD1006 /* NotificationExceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02C81702177729000CD1006 /* NotificationExceptions.swift */; };
D02C81732177AC5900CD1006 /* NotificationSearchItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02C81722177AC5900CD1006 /* NotificationSearchItem.swift */; };
D02D60AE206BD47300FEFE1E /* SecureIdDocumentTypeSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02D60AD206BD47300FEFE1E /* SecureIdDocumentTypeSelectionController.swift */; }; D02D60AE206BD47300FEFE1E /* SecureIdDocumentTypeSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02D60AD206BD47300FEFE1E /* SecureIdDocumentTypeSelectionController.swift */; };
D02D60B1206C189900FEFE1E /* SecureIdPlaintextFormController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02D60B0206C189900FEFE1E /* SecureIdPlaintextFormController.swift */; }; D02D60B1206C189900FEFE1E /* SecureIdPlaintextFormController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02D60B0206C189900FEFE1E /* SecureIdPlaintextFormController.swift */; };
D02D60B3206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02D60B2206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift */; }; D02D60B3206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02D60B2206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift */; };
@ -1046,6 +1056,12 @@
0941A9A3210B0E2E00EBE194 /* OpenInAppIconResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInAppIconResources.swift; sourceTree = "<group>"; }; 0941A9A3210B0E2E00EBE194 /* OpenInAppIconResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInAppIconResources.swift; sourceTree = "<group>"; };
0941A9A5210B822D00EBE194 /* OpenInOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInOptions.swift; sourceTree = "<group>"; }; 0941A9A5210B822D00EBE194 /* OpenInOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInOptions.swift; sourceTree = "<group>"; };
0952D1742176DEB500194860 /* NotificationMuteSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMuteSettingsController.swift; sourceTree = "<group>"; }; 0952D1742176DEB500194860 /* NotificationMuteSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMuteSettingsController.swift; sourceTree = "<group>"; };
0952D1762177FB5400194860 /* WatchPresetSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchPresetSettings.swift; sourceTree = "<group>"; };
096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyBridgeAudio.swift; sourceTree = "<group>"; };
096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGBridgeAudioEncoder.m; sourceTree = "<group>"; };
096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGBridgeAudioEncoder.h; sourceTree = "<group>"; };
096C98BD21787C6700C211FF /* TGBridgeAudioDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGBridgeAudioDecoder.h; sourceTree = "<group>"; };
096C98BE21787C6700C211FF /* TGBridgeAudioDecoder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TGBridgeAudioDecoder.mm; sourceTree = "<group>"; };
09797872210633CD0077D77F /* InstantPageSettingsButtonItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageSettingsButtonItemNode.swift; sourceTree = "<group>"; }; 09797872210633CD0077D77F /* InstantPageSettingsButtonItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageSettingsButtonItemNode.swift; sourceTree = "<group>"; };
0979787B210642CB0077D77F /* WebEmbedPlayerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebEmbedPlayerNode.swift; sourceTree = "<group>"; }; 0979787B210642CB0077D77F /* WebEmbedPlayerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebEmbedPlayerNode.swift; sourceTree = "<group>"; };
0979787D210646C00077D77F /* YoutubeEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YoutubeEmbedImplementation.swift; sourceTree = "<group>"; }; 0979787D210646C00077D77F /* YoutubeEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YoutubeEmbedImplementation.swift; sourceTree = "<group>"; };
@ -1067,6 +1083,8 @@
09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySecureIdScanController.swift; sourceTree = "<group>"; }; 09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySecureIdScanController.swift; sourceTree = "<group>"; };
09C3466C2167D63A00B76780 /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = "<group>"; }; 09C3466C2167D63A00B76780 /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = "<group>"; };
09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListWebsiteItem.swift; sourceTree = "<group>"; }; 09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListWebsiteItem.swift; sourceTree = "<group>"; };
09D304142173C0E900C00567 /* WatchManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchManager.swift; sourceTree = "<group>"; };
09D304172173C15700C00567 /* WatchSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchSettingsController.swift; sourceTree = "<group>"; };
09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRouteActionSheetItem.swift; sourceTree = "<group>"; }; 09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRouteActionSheetItem.swift; sourceTree = "<group>"; };
D00219051DDD1C9E00BE708A /* ImageContainingNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageContainingNode.swift; sourceTree = "<group>"; }; D00219051DDD1C9E00BE708A /* ImageContainingNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageContainingNode.swift; sourceTree = "<group>"; };
D002A0D01E9B99F500A81812 /* SoftwareVideoSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SoftwareVideoSource.swift; sourceTree = "<group>"; }; D002A0D01E9B99F500A81812 /* SoftwareVideoSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SoftwareVideoSource.swift; sourceTree = "<group>"; };
@ -1238,6 +1256,8 @@
D02B676220800A00001A864A /* StickerPaneSearchBarPlaceholderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPaneSearchBarPlaceholderItem.swift; sourceTree = "<group>"; }; D02B676220800A00001A864A /* StickerPaneSearchBarPlaceholderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPaneSearchBarPlaceholderItem.swift; sourceTree = "<group>"; };
D02BE0701D91814C000889C2 /* ChatHistoryGridNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryGridNode.swift; sourceTree = "<group>"; }; D02BE0701D91814C000889C2 /* ChatHistoryGridNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryGridNode.swift; sourceTree = "<group>"; };
D02BE0761D9190EF000889C2 /* GridMessageItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridMessageItem.swift; sourceTree = "<group>"; }; D02BE0761D9190EF000889C2 /* GridMessageItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridMessageItem.swift; sourceTree = "<group>"; };
D02C81702177729000CD1006 /* NotificationExceptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationExceptions.swift; sourceTree = "<group>"; };
D02C81722177AC5900CD1006 /* NotificationSearchItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSearchItem.swift; sourceTree = "<group>"; };
D02D60AD206BD47300FEFE1E /* SecureIdDocumentTypeSelectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdDocumentTypeSelectionController.swift; sourceTree = "<group>"; }; D02D60AD206BD47300FEFE1E /* SecureIdDocumentTypeSelectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdDocumentTypeSelectionController.swift; sourceTree = "<group>"; };
D02D60B0206C189900FEFE1E /* SecureIdPlaintextFormController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPlaintextFormController.swift; sourceTree = "<group>"; }; D02D60B0206C189900FEFE1E /* SecureIdPlaintextFormController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPlaintextFormController.swift; sourceTree = "<group>"; };
D02D60B2206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPlaintextFormControllerNode.swift; sourceTree = "<group>"; }; D02D60B2206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPlaintextFormControllerNode.swift; sourceTree = "<group>"; };
@ -2217,6 +2237,18 @@
name = "Open In"; name = "Open In";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0965C7152178738A007C94D0 /* Bridge Audio */ = {
isa = PBXGroup;
children = (
096C98BD21787C6700C211FF /* TGBridgeAudioDecoder.h */,
096C98BE21787C6700C211FF /* TGBridgeAudioDecoder.mm */,
096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */,
096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */,
096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */,
);
name = "Bridge Audio";
sourceTree = "<group>";
};
0979787F21065EAA0077D77F /* Web Embed */ = { 0979787F21065EAA0077D77F /* Web Embed */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2249,6 +2281,14 @@
name = "Web Embed"; name = "Web Embed";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
09D304162173C13500C00567 /* Watch */ = {
isa = PBXGroup;
children = (
09D304172173C15700C00567 /* WatchSettingsController.swift */,
);
name = Watch;
sourceTree = "<group>";
};
D00C7CDA1E3776CA0080C3D5 /* Secret Preview */ = { D00C7CDA1E3776CA0080C3D5 /* Secret Preview */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2437,6 +2477,17 @@
name = "Grid Items"; name = "Grid Items";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D02C816F2177715A00CD1006 /* Notifications */ = {
isa = PBXGroup;
children = (
D0579E6D2179178700495DC7 /* exceptions */,
D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */,
D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */,
D02C81722177AC5900CD1006 /* NotificationSearchItem.swift */,
);
name = Notifications;
sourceTree = "<group>";
};
D02D60AF206C188000FEFE1E /* Plaintext Fields */ = { D02D60AF206C188000FEFE1E /* Plaintext Fields */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2785,6 +2836,14 @@
name = "Avatar Gallery"; name = "Avatar Gallery";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D0579E6D2179178700495DC7 /* exceptions */ = {
isa = PBXGroup;
children = (
D02C81702177729000CD1006 /* NotificationExceptions.swift */,
);
name = exceptions;
sourceTree = "<group>";
};
D05BFB4F1EA96EC100909D38 /* Themes */ = { D05BFB4F1EA96EC100909D38 /* Themes */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2888,6 +2947,7 @@
D07551891DDA4C7C0073E051 /* Legacy Components */ = { D07551891DDA4C7C0073E051 /* Legacy Components */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0965C7152178738A007C94D0 /* Bridge Audio */,
D04BB2C61E48797500650E93 /* RMIntro */, D04BB2C61E48797500650E93 /* RMIntro */,
D067B4AE211C916D00796039 /* Channel Intro */, D067B4AE211C916D00796039 /* Channel Intro */,
D075518A1DDA4D7D0073E051 /* LegacyController.swift */, D075518A1DDA4D7D0073E051 /* LegacyController.swift */,
@ -3003,6 +3063,7 @@
D048B33A203C777500038D05 /* RenderedTotalUnreadCount.swift */, D048B33A203C777500038D05 /* RenderedTotalUnreadCount.swift */,
D06ECFCA20B8448E00C576C2 /* ContactSynchronizationSettings.swift */, D06ECFCA20B8448E00C576C2 /* ContactSynchronizationSettings.swift */,
D08A10BA211DF7A80077488B /* StickerSettings.swift */, D08A10BA211DF7A80077488B /* StickerSettings.swift */,
0952D1762177FB5400194860 /* WatchPresetSettings.swift */,
); );
name = Settings; name = Settings;
sourceTree = "<group>"; sourceTree = "<group>";
@ -4230,16 +4291,16 @@
D0F69E791D6B8C3B0046BCD6 /* Settings */ = { D0F69E791D6B8C3B0046BCD6 /* Settings */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D02C816F2177715A00CD1006 /* Notifications */,
D0FA0AC21E7742CE005BB9B7 /* Privacy and Security */, D0FA0AC21E7742CE005BB9B7 /* Privacy and Security */,
D0C9323A1E0B4AD40074F044 /* Data and Storage */, D0C9323A1E0B4AD40074F044 /* Data and Storage */,
D0FA0AC31E7742EE005BB9B7 /* Stickers */, D0FA0AC31E7742EE005BB9B7 /* Stickers */,
D05BFB4F1EA96EC100909D38 /* Themes */, D05BFB4F1EA96EC100909D38 /* Themes */,
09D304162173C13500C00567 /* Watch */,
D0AF7C441ED84BB000CD8E0F /* Language Selection */, D0AF7C441ED84BB000CD8E0F /* Language Selection */,
D0CB27D020C17A6D001ACF93 /* Terms of Service */, D0CB27D020C17A6D001ACF93 /* Terms of Service */,
D01B279A1E39386C0022A4C0 /* SettingsController.swift */, D01B279A1E39386C0022A4C0 /* SettingsController.swift */,
D08BDF651FA8CB10009D08E1 /* EditSettingsController.swift */, D08BDF651FA8CB10009D08E1 /* EditSettingsController.swift */,
D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */,
D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */,
D0CE1BD21E51BC6100404327 /* DebugController.swift */, D0CE1BD21E51BC6100404327 /* DebugController.swift */,
D03E5E081E55C49C0029569A /* DebugAccountsController.swift */, D03E5E081E55C49C0029569A /* DebugAccountsController.swift */,
D0528E671E65CB2C00E2FEF5 /* UsernameSetupController.swift */, D0528E671E65CB2C00E2FEF5 /* UsernameSetupController.swift */,
@ -4289,6 +4350,7 @@
D0383ED5207D19BC00C45548 /* Emoji */, D0383ED5207D19BC00C45548 /* Emoji */,
D0B69C3A20EBD8B3003632C7 /* Device Access */, D0B69C3A20EBD8B3003632C7 /* Device Access */,
D01C7EFE1EF9D434008305F1 /* Device Contacts */, D01C7EFE1EF9D434008305F1 /* Device Contacts */,
09D304142173C0E900C00567 /* WatchManager.swift */,
D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */, D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */,
D08775081E3E59DE00A97350 /* PeerNotificationSoundStrings.swift */, D08775081E3E59DE00A97350 /* PeerNotificationSoundStrings.swift */,
D0F69E931D6B8C9B0046BCD6 /* ProgressiveImage.swift */, D0F69E931D6B8C9B0046BCD6 /* ProgressiveImage.swift */,
@ -4471,6 +4533,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D0E9BA221F05577700F079A4 /* STPCard.h in Headers */, D0E9BA221F05577700F079A4 /* STPCard.h in Headers */,
096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */,
D0E9BA591F055A2200F079A4 /* STPWeakStrongMacros.h in Headers */, D0E9BA591F055A2200F079A4 /* STPWeakStrongMacros.h in Headers */,
D0E9BADE1F0574D800F079A4 /* STPBackendAPIAdapter.h in Headers */, D0E9BADE1F0574D800F079A4 /* STPBackendAPIAdapter.h in Headers */,
D0E9BAD11F0573C000F079A4 /* STPToken.h in Headers */, D0E9BAD11F0573C000F079A4 /* STPToken.h in Headers */,
@ -4501,6 +4564,7 @@
D06F31E22135829B001A0F12 /* EDSunriseSet.h in Headers */, D06F31E22135829B001A0F12 /* EDSunriseSet.h in Headers */,
D0E9BA531F0559DA00F079A4 /* STPImageLibrary+Private.h in Headers */, D0E9BA531F0559DA00F079A4 /* STPImageLibrary+Private.h in Headers */,
D0E9BA601F055A4300F079A4 /* STPDelegateProxy.h in Headers */, D0E9BA601F055A4300F079A4 /* STPDelegateProxy.h in Headers */,
096C98C121787C6700C211FF /* TGBridgeAudioDecoder.h in Headers */,
D0E9BADF1F0574D800F079A4 /* STPDispatchFunctions.h in Headers */, D0E9BADF1F0574D800F079A4 /* STPDispatchFunctions.h in Headers */,
D0E9BACB1F05738600F079A4 /* STPAPIPostRequest.h in Headers */, D0E9BACB1F05738600F079A4 /* STPAPIPostRequest.h in Headers */,
D0E9BA561F055A0B00F079A4 /* STPFormTextField.h in Headers */, D0E9BA561F055A0B00F079A4 /* STPFormTextField.h in Headers */,
@ -4723,6 +4787,7 @@
D0208ADC1FA346A4001F0D5F /* RaiseToListen.swift in Sources */, D0208ADC1FA346A4001F0D5F /* RaiseToListen.swift in Sources */,
D0EB41F91F30E5B700838FE6 /* LegacyPeerAvatarPlaceholderDataSource.swift in Sources */, D0EB41F91F30E5B700838FE6 /* LegacyPeerAvatarPlaceholderDataSource.swift in Sources */,
D0EC6CBB1EB9F58800EBF1C3 /* texture_helper.m in Sources */, D0EC6CBB1EB9F58800EBF1C3 /* texture_helper.m in Sources */,
09D304182173C15700C00567 /* WatchSettingsController.swift in Sources */,
D0EC6CBC1EB9F58800EBF1C3 /* LegacyController.swift in Sources */, D0EC6CBC1EB9F58800EBF1C3 /* LegacyController.swift in Sources */,
D0EC6CBD1EB9F58800EBF1C3 /* LegacyControllerNode.swift in Sources */, D0EC6CBD1EB9F58800EBF1C3 /* LegacyControllerNode.swift in Sources */,
D079FCE91F06A76C0038FADE /* Notices.swift in Sources */, D079FCE91F06A76C0038FADE /* Notices.swift in Sources */,
@ -4847,6 +4912,7 @@
D0ACCB1A1EC5E0C20079D8BF /* CallControllerKeyPreviewNode.swift in Sources */, D0ACCB1A1EC5E0C20079D8BF /* CallControllerKeyPreviewNode.swift in Sources */,
D0E9BA611F055A4300F079A4 /* STPDelegateProxy.m in Sources */, D0E9BA611F055A4300F079A4 /* STPDelegateProxy.m in Sources */,
D0EC6CF91EB9F58800EBF1C3 /* MediaManager.swift in Sources */, D0EC6CF91EB9F58800EBF1C3 /* MediaManager.swift in Sources */,
096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */,
D01776B81F1D6FB30044446D /* RadialProgressContentNode.swift in Sources */, D01776B81F1D6FB30044446D /* RadialProgressContentNode.swift in Sources */,
D0EC6CFA1EB9F58800EBF1C3 /* ManagedAudioSession.swift in Sources */, D0EC6CFA1EB9F58800EBF1C3 /* ManagedAudioSession.swift in Sources */,
D0EB5ADF1F798033004E89B6 /* PeerMediaCollectionEmptyNode.swift in Sources */, D0EB5ADF1F798033004E89B6 /* PeerMediaCollectionEmptyNode.swift in Sources */,
@ -4856,6 +4922,7 @@
D0EC6CFD1EB9F58800EBF1C3 /* AudioWaveform.swift in Sources */, D0EC6CFD1EB9F58800EBF1C3 /* AudioWaveform.swift in Sources */,
D0EC6CFF1EB9F58800EBF1C3 /* OverlayMediaController.swift in Sources */, D0EC6CFF1EB9F58800EBF1C3 /* OverlayMediaController.swift in Sources */,
D0EC6D001EB9F58800EBF1C3 /* OverlayMediaControllerNode.swift in Sources */, D0EC6D001EB9F58800EBF1C3 /* OverlayMediaControllerNode.swift in Sources */,
D02C81712177729000CD1006 /* NotificationExceptions.swift in Sources */,
D0EC6D021EB9F58800EBF1C3 /* diag_range.c in Sources */, D0EC6D021EB9F58800EBF1C3 /* diag_range.c in Sources */,
D0E9BA1A1F05574500F079A4 /* STPPaymentCardTextField.m in Sources */, D0E9BA1A1F05574500F079A4 /* STPPaymentCardTextField.m in Sources */,
D0EC6D031EB9F58800EBF1C3 /* opus_header.c in Sources */, D0EC6D031EB9F58800EBF1C3 /* opus_header.c in Sources */,
@ -4984,6 +5051,7 @@
D0EC6D3F1EB9F58800EBF1C3 /* MediaNavigationAccessoryPanel.swift in Sources */, D0EC6D3F1EB9F58800EBF1C3 /* MediaNavigationAccessoryPanel.swift in Sources */,
D0E9BA3B1F0558E800F079A4 /* NSString+Stripe.m in Sources */, D0E9BA3B1F0558E800F079A4 /* NSString+Stripe.m in Sources */,
D0CE8CE51F6F354400AA2DB0 /* ChatTextInputAccessoryItem.swift in Sources */, D0CE8CE51F6F354400AA2DB0 /* ChatTextInputAccessoryItem.swift in Sources */,
096C98BA21787A5C00C211FF /* LegacyBridgeAudio.swift in Sources */,
D0EC6D401EB9F58800EBF1C3 /* MediaNavigationAccessoryContainerNode.swift in Sources */, D0EC6D401EB9F58800EBF1C3 /* MediaNavigationAccessoryContainerNode.swift in Sources */,
D0E266FD1F66706500BFC79F /* ChatBubbleVideoDecoration.swift in Sources */, D0E266FD1F66706500BFC79F /* ChatBubbleVideoDecoration.swift in Sources */,
D0EC6D411EB9F58800EBF1C3 /* MediaNavigationAccessoryHeaderNode.swift in Sources */, D0EC6D411EB9F58800EBF1C3 /* MediaNavigationAccessoryHeaderNode.swift in Sources */,
@ -5009,6 +5077,7 @@
D0208ADA1FA34017001F0D5F /* DeviceProximityManager.m in Sources */, D0208ADA1FA34017001F0D5F /* DeviceProximityManager.m in Sources */,
D04281FC200E61BC009DDE36 /* ChatRecentActionsInteraction.swift in Sources */, D04281FC200E61BC009DDE36 /* ChatRecentActionsInteraction.swift in Sources */,
D0EC6D561EB9F58800EBF1C3 /* ChatHistoryNode.swift in Sources */, D0EC6D561EB9F58800EBF1C3 /* ChatHistoryNode.swift in Sources */,
096C98C221787C6700C211FF /* TGBridgeAudioDecoder.mm in Sources */,
D0EC6D571EB9F58800EBF1C3 /* ChatHistoryListNode.swift in Sources */, D0EC6D571EB9F58800EBF1C3 /* ChatHistoryListNode.swift in Sources */,
D0EC6D581EB9F58800EBF1C3 /* ChatHistoryGridNode.swift in Sources */, D0EC6D581EB9F58800EBF1C3 /* ChatHistoryGridNode.swift in Sources */,
D0B2F76E2052B59F00D3BFB9 /* InviteContactsController.swift in Sources */, D0B2F76E2052B59F00D3BFB9 /* InviteContactsController.swift in Sources */,
@ -5041,6 +5110,7 @@
D0EC6D671EB9F58800EBF1C3 /* ContactListNameIndexHeader.swift in Sources */, D0EC6D671EB9F58800EBF1C3 /* ContactListNameIndexHeader.swift in Sources */,
D0CE6F6E213EDF8800BCD44B /* SecureIdAuthPasswordSetupContentNode.swift in Sources */, D0CE6F6E213EDF8800BCD44B /* SecureIdAuthPasswordSetupContentNode.swift in Sources */,
D07E413D208A494D00FCA8F0 /* ProxyServerActionSheetController.swift in Sources */, D07E413D208A494D00FCA8F0 /* ProxyServerActionSheetController.swift in Sources */,
D02C81732177AC5900CD1006 /* NotificationSearchItem.swift in Sources */,
D0EC6D681EB9F58800EBF1C3 /* AuthorizationSequenceController.swift in Sources */, D0EC6D681EB9F58800EBF1C3 /* AuthorizationSequenceController.swift in Sources */,
D0EC6D691EB9F58800EBF1C3 /* AuthorizationSequenceSplashController.swift in Sources */, D0EC6D691EB9F58800EBF1C3 /* AuthorizationSequenceSplashController.swift in Sources */,
D0EC6D6A1EB9F58800EBF1C3 /* AuthorizationSequenceSplashControllerNode.swift in Sources */, D0EC6D6A1EB9F58800EBF1C3 /* AuthorizationSequenceSplashControllerNode.swift in Sources */,
@ -5139,6 +5209,7 @@
D0147BA7206E8B4F00E40378 /* SecureIdAuthAcceptNode.swift in Sources */, D0147BA7206E8B4F00E40378 /* SecureIdAuthAcceptNode.swift in Sources */,
D0E8174E2011FC3800B82BBB /* ChatMessageEventLogPreviousDescriptionContentNode.swift in Sources */, D0E8174E2011FC3800B82BBB /* ChatMessageEventLogPreviousDescriptionContentNode.swift in Sources */,
D0EC6D981EB9F58900EBF1C3 /* ChatMessageItemView.swift in Sources */, D0EC6D981EB9F58900EBF1C3 /* ChatMessageItemView.swift in Sources */,
09D304152173C0E900C00567 /* WatchManager.swift in Sources */,
D039FB1921711B5D00BD1BAD /* PlatformVideoContent.swift in Sources */, D039FB1921711B5D00BD1BAD /* PlatformVideoContent.swift in Sources */,
D0CAD8FD20AE467D00ACD96E /* PeerChannelMemberCategoriesContextsManager.swift in Sources */, D0CAD8FD20AE467D00ACD96E /* PeerChannelMemberCategoriesContextsManager.swift in Sources */,
D073D2DB1FB61DA9009E1DA2 /* CallListSettings.swift in Sources */, D073D2DB1FB61DA9009E1DA2 /* CallListSettings.swift in Sources */,
@ -5368,6 +5439,7 @@
D0EC6E171EB9F58900EBF1C3 /* InstantPageTextStyleStack.swift in Sources */, D0EC6E171EB9F58900EBF1C3 /* InstantPageTextStyleStack.swift in Sources */,
D0EC6E181EB9F58900EBF1C3 /* InstantPageTextItem.swift in Sources */, D0EC6E181EB9F58900EBF1C3 /* InstantPageTextItem.swift in Sources */,
D01C06B51FBB7720001561AB /* ChatMediaInputSettingsItem.swift in Sources */, D01C06B51FBB7720001561AB /* ChatMediaInputSettingsItem.swift in Sources */,
0952D1772177FB5400194860 /* WatchPresetSettings.swift in Sources */,
D091C7A61F8ECEA300D7DE13 /* SettingsThemeWallpaperNode.swift in Sources */, D091C7A61F8ECEA300D7DE13 /* SettingsThemeWallpaperNode.swift in Sources */,
D0EC6E191EB9F58900EBF1C3 /* InstantPageAnchorItem.swift in Sources */, D0EC6E191EB9F58900EBF1C3 /* InstantPageAnchorItem.swift in Sources */,
D05677531F4CA0D0001B723E /* InstantPagePeerReferenceNode.swift in Sources */, D05677531F4CA0D0001B723E /* InstantPagePeerReferenceNode.swift in Sources */,

View File

@ -15,6 +15,9 @@ public struct ChatListNodePeersFilter: OptionSet {
public static let onlyWriteable = ChatListNodePeersFilter(rawValue: 1 << 0) public static let onlyWriteable = ChatListNodePeersFilter(rawValue: 1 << 0)
public static let onlyUsers = ChatListNodePeersFilter(rawValue: 1 << 1) public static let onlyUsers = ChatListNodePeersFilter(rawValue: 1 << 1)
public static let onlyGroups = ChatListNodePeersFilter(rawValue: 1 << 2) public static let onlyGroups = ChatListNodePeersFilter(rawValue: 1 << 2)
public static let withoutSecretChats = ChatListNodePeersFilter(rawValue: 1 << 3)
} }
enum ChatListNodeMode { enum ChatListNodeMode {

View File

@ -220,12 +220,25 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
if view.laterIndex == nil && savedMessagesPeer == nil { if view.laterIndex == nil && savedMessagesPeer == nil {
pinnedIndexOffset = UInt16(view.additionalItemEntries.count) pinnedIndexOffset = UInt16(view.additionalItemEntries.count)
} }
loop: for entry in view.entries { loop: for entry in view.entries {
switch entry { switch entry {
case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo): case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo):
if let savedMessagesPeer = savedMessagesPeer, savedMessagesPeer.id == index.messageIndex.id.peerId { if let savedMessagesPeer = savedMessagesPeer, savedMessagesPeer.id == index.messageIndex.id.peerId {
continue loop continue loop
} }
switch mode {
case let .peers(filter):
if filter.contains(.withoutSecretChats) {
if index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat {
continue
}
}
default:
break
}
result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: message, readState: combinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: false)) result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: message, readState: combinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: false))
case let .HoleEntry(hole): case let .HoleEntry(hole):
result.append(.HoleEntry(hole, theme: state.presentationData.theme)) result.append(.HoleEntry(hole, theme: state.presentationData.theme))

View File

@ -26,6 +26,7 @@ private var telegramUIDeclaredEncodables: Void = {
declareEncodable(CachedChannelAdminIds.self, f: { CachedChannelAdminIds(decoder: $0) }) declareEncodable(CachedChannelAdminIds.self, f: { CachedChannelAdminIds(decoder: $0) })
declareEncodable(StickerSettings.self, f: { StickerSettings(decoder: $0) }) declareEncodable(StickerSettings.self, f: { StickerSettings(decoder: $0) })
declareEncodable(InstantPagePresentationSettings.self, f: { InstantPagePresentationSettings(decoder: $0) }) declareEncodable(InstantPagePresentationSettings.self, f: { InstantPagePresentationSettings(decoder: $0) })
declareEncodable(WatchPresetSettings.self, f: { WatchPresetSettings(decoder: $0) })
return return
}() }()

View File

@ -3,7 +3,7 @@ import TelegramCore
import Postbox import Postbox
import SwiftSignalKit import SwiftSignalKit
func freeMediaFileInteractiveFetched(account: Account, fileReference: FileMediaReference) -> Signal<FetchResourceSourceType, NoError> { public func freeMediaFileInteractiveFetched(account: Account, fileReference: FileMediaReference) -> Signal<FetchResourceSourceType, NoError> {
return fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(fileReference.media.resource)) return fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(fileReference.media.resource))
} }
@ -25,7 +25,7 @@ private func fetchCategoryForFile(_ file: TelegramMediaFile) -> FetchManagerCate
} }
} }
func messageMediaFileInteractiveFetched(account: Account, message: Message, file: TelegramMediaFile, userInitiated: Bool) -> Signal<Void, NoError> { public func messageMediaFileInteractiveFetched(account: Account, message: Message, file: TelegramMediaFile, userInitiated: Bool) -> Signal<Void, NoError> {
let mediaReference = AnyMediaReference.message(message: MessageReference(message), media: file) let mediaReference = AnyMediaReference.message(message: MessageReference(message), media: file)
return account.telegramApplicationContext.fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(message.id.peerId), locationKey: .messageId(message.id), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: userInitiated) return account.telegramApplicationContext.fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(message.id.peerId), locationKey: .messageId(message.id), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: userInitiated)
} }
@ -34,7 +34,7 @@ func messageMediaFileCancelInteractiveFetch(account: Account, messageId: Message
account.telegramApplicationContext.fetchManager.cancelInteractiveFetches(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource) account.telegramApplicationContext.fetchManager.cancelInteractiveFetches(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource)
} }
func messageMediaImageInteractiveFetched(account: Account, message: Message, image: TelegramMediaImage, resource: MediaResource, storeToDownloadsPeerType: AutomaticMediaDownloadPeerType?) -> Signal<Void, NoError> { public func messageMediaImageInteractiveFetched(account: Account, message: Message, image: TelegramMediaImage, resource: MediaResource, storeToDownloadsPeerType: AutomaticMediaDownloadPeerType?) -> Signal<Void, NoError> {
let mediaReference = AnyMediaReference.message(message: MessageReference(message), media: image) let mediaReference = AnyMediaReference.message(message: MessageReference(message), media: image)
return account.telegramApplicationContext.fetchManager.interactivelyFetched(category: .image, location: .chat(message.id.peerId), locationKey: .messageId(message.id), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(resource), statsCategory: .image, elevatedPriority: false, userInitiated: true, storeToDownloadsPeerType: storeToDownloadsPeerType) return account.telegramApplicationContext.fetchManager.interactivelyFetched(category: .image, location: .chat(message.id.peerId), locationKey: .messageId(message.id), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(resource), statsCategory: .image, elevatedPriority: false, userInitiated: true, storeToDownloadsPeerType: storeToDownloadsPeerType)
} }

View File

@ -20,6 +20,7 @@ enum ItemListNavigationButtonStyle {
enum ItemListNavigationButtonContentIcon { enum ItemListNavigationButtonContentIcon {
case search case search
case add
} }
enum ItemListNavigationButtonContent: Equatable { enum ItemListNavigationButtonContent: Equatable {
@ -271,6 +272,8 @@ class ItemListController<Entry: ItemListNodeEntry>: ViewController {
switch icon { switch icon {
case .search: case .search:
image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.theme) image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.theme)
case .add:
image = PresentationResourcesRootController.navigationAddIcon(controllerState.theme)
} }
item = UIBarButtonItem(image: image, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed)) item = UIBarButtonItem(image: image, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed))
} }
@ -324,6 +327,8 @@ class ItemListController<Entry: ItemListNodeEntry>: ViewController {
switch icon { switch icon {
case .search: case .search:
image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.theme) image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.theme)
case .add:
image = PresentationResourcesRootController.navigationAddIcon(controllerState.theme)
} }
item = UIBarButtonItem(image: image, style: style.barButtonItemStyle, target: strongSelf, action: action) item = UIBarButtonItem(image: image, style: style.barButtonItemStyle, target: strongSelf, action: action)
} }

View File

@ -79,8 +79,8 @@ final class ItemListPeerItem: ListViewItem, ItemListItem {
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let removePeer: (PeerId) -> Void let removePeer: (PeerId) -> Void
let toggleUpdated: ((Bool) -> Void)? let toggleUpdated: ((Bool) -> Void)?
let hasTopStripe: Bool
init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, account: Account, peer: Peer, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, presence: PeerPresence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, toggleUpdated: ((Bool) -> Void)? = nil) { init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, account: Account, peer: Peer, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, presence: PeerPresence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, hasTopStripe: Bool = true) {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
@ -100,6 +100,7 @@ final class ItemListPeerItem: ListViewItem, ItemListItem {
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.removePeer = removePeer self.removePeer = removePeer
self.toggleUpdated = toggleUpdated self.toggleUpdated = toggleUpdated
self.hasTopStripe = hasTopStripe
} }
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) { func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
@ -507,7 +508,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
case .sameSection(false): case .sameSection(false):
strongSelf.topStripeNode.isHidden = true strongSelf.topStripeNode.isHidden = true
default: default:
strongSelf.topStripeNode.isHidden = false strongSelf.topStripeNode.isHidden = !item.hasTopStripe
} }
let bottomStripeInset: CGFloat let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat let bottomStripeOffset: CGFloat

View File

@ -0,0 +1,26 @@
import Foundation
import SwiftSignalKit
import TelegramUIPrivateModule
public func legacyDecodeOpusAudio(path: String, outputPath: String) -> Signal<String, NoError> {
return Signal { subscriber in
let decoder = TGBridgeAudioDecoder(url: URL(fileURLWithPath: path), outputUrl: URL(fileURLWithPath: outputPath))
decoder?.start(completion: {
subscriber.putNext(outputPath)
subscriber.putCompletion()
})
return EmptyDisposable
}
}
public func legacyEncodeOpusAudio(path: String) -> Signal<(Data?, Int32), NoError> {
return Signal { subscriber in
let encoder = TGBridgeAudioEncoder(url: URL(fileURLWithPath: path))
encoder?.start(completion: { (dataItem, duration) in
subscriber.putNext((dataItem?.data(), duration))
subscriber.putCompletion()
})
return EmptyDisposable
}
}

View File

@ -0,0 +1,995 @@
import Foundation
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
private final class NotificationExceptionArguments {
let account: Account
let activateSearch:()->Void
let changeNotifications: (PeerId, TelegramPeerNotificationSettings) -> Void
let selectPeer: ()->Void
init(account: Account, activateSearch:@escaping() -> Void, changeNotifications: @escaping(PeerId, TelegramPeerNotificationSettings) -> Void, selectPeer: @escaping()->Void) {
self.account = account
self.activateSearch = activateSearch
self.changeNotifications = changeNotifications
self.selectPeer = selectPeer
}
}
private enum NotificationExceptionEntryId: Hashable {
case search
case peerId(Int64)
var hashValue: Int {
switch self {
case .search:
return 0
case let .peerId(peerId):
return peerId.hashValue
}
}
static func <(lhs: NotificationExceptionEntryId, rhs: NotificationExceptionEntryId) -> Bool {
return lhs.hashValue < rhs.hashValue
}
static func ==(lhs: NotificationExceptionEntryId, rhs: NotificationExceptionEntryId) -> Bool {
switch lhs {
case .search:
switch rhs {
case .search:
return true
default:
return false
}
case let .peerId(lhsId):
switch rhs {
case let .peerId(rhsId):
return lhsId == rhsId
default:
return false
}
}
}
}
private enum NotificationExceptionSectionId : ItemListSectionId {
case general = 0
}
private enum NotificationExceptionEntry : ItemListNodeEntry {
var section: ItemListSectionId {
return NotificationExceptionSectionId.general.rawValue
}
typealias ItemGenerationArguments = NotificationExceptionArguments
case search(PresentationTheme, PresentationStrings)
case peer(Int, Peer, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, String, TelegramPeerNotificationSettings)
func item(_ arguments: NotificationExceptionArguments) -> ListViewItem {
switch self {
case let .search(theme, strings):
return NotificationSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: {
arguments.activateSearch()
})
case let .peer(_, peer, theme, strings, dateTimeFormat, value, settings):
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: arguments.account, peer: peer, presence: nil, text: .text(value), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, sectionId: self.section, action: {
arguments.changeNotifications(peer.id, settings)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
}, removePeer: { peerId in
}, hasTopStripe: false)
}
}
var stableId: NotificationExceptionEntryId {
switch self {
case .search:
return .search
case let .peer(_, peer, _, _, _, _, _):
return .peerId(peer.id.toInt64())
}
}
static func == (lhs: NotificationExceptionEntry, rhs: NotificationExceptionEntry) -> Bool {
switch lhs {
case let .search(lhsTheme, lhsStrings):
switch rhs {
case let .search(rhsTheme, rhsStrings):
return lhsTheme === rhsTheme && lhsStrings === rhsStrings
default:
return false
}
case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsValue, lhsSettings):
switch rhs {
case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsValue, rhsSettings):
return lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsDateTimeFormat == rhsDateTimeFormat && lhsIndex == rhsIndex && lhsPeer.isEqual(rhsPeer) && lhsValue == rhsValue && lhsSettings == rhsSettings
default:
return false
}
}
}
static func <(lhs: NotificationExceptionEntry, rhs: NotificationExceptionEntry) -> Bool {
switch lhs {
case .search:
return true
case let .peer(lhsIndex, _, _, _, _, _, _):
switch rhs {
case .search:
return false
case let .peer(rhsIndex, _, _, _, _, _, _):
return lhsIndex < rhsIndex
}
}
}
}
private final class NotificationExceptionState : Equatable {
let mode:NotificationExceptionMode
let isSearchMode: Bool
init(mode: NotificationExceptionMode, isSearchMode: Bool = false) {
self.mode = mode
self.isSearchMode = isSearchMode
}
func withUpdatedSearchMode(_ isSearchMode: Bool) -> NotificationExceptionState {
return NotificationExceptionState.init(mode: mode, isSearchMode: isSearchMode)
}
func withUpdatedPeerIdSound(_ peerId: PeerId, _ sound: PeerMessageSound) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode.withUpdatedPeerIdSound(peerId, sound), isSearchMode: isSearchMode)
}
func withUpdatedPeerIdMuteInterval(_ peerId: PeerId, _ muteInterval: Int32?) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode.withUpdatedPeerIdMuteInterval(peerId, muteInterval), isSearchMode: isSearchMode)
}
static func == (lhs: NotificationExceptionState, rhs: NotificationExceptionState) -> Bool {
return lhs.mode == rhs.mode && lhs.isSearchMode == rhs.isSearchMode
}
}
public struct NotificationExceptionWrapper : Equatable {
let settings: TelegramPeerNotificationSettings
let date: TimeInterval?
init(settings: TelegramPeerNotificationSettings, date: TimeInterval? = nil) {
self.settings = settings
self.date = date
}
func withUpdatedSettings(_ settings: TelegramPeerNotificationSettings) -> NotificationExceptionWrapper {
return NotificationExceptionWrapper(settings: settings, date: self.date)
}
func updateSettings(_ f: (TelegramPeerNotificationSettings) -> TelegramPeerNotificationSettings) -> NotificationExceptionWrapper {
return NotificationExceptionWrapper(settings: f(self.settings), date: self.date)
}
func withUpdatedDate(_ date: TimeInterval) -> NotificationExceptionWrapper {
return NotificationExceptionWrapper(settings: self.settings, date: date)
}
}
public enum NotificationExceptionMode : Equatable {
public static func == (lhs: NotificationExceptionMode, rhs: NotificationExceptionMode) -> Bool {
switch lhs {
case let .users(lhsValue):
if case let .users(rhsValue) = rhs {
return lhsValue == rhsValue
} else {
return false
}
case let .groups(lhsValue):
if case let .groups(rhsValue) = rhs {
return lhsValue == rhsValue
} else {
return false
}
}
}
case users([PeerId : NotificationExceptionWrapper])
case groups([PeerId : NotificationExceptionWrapper])
func withUpdatedPeerIdSound(_ peerId: PeerId, _ sound: PeerMessageSound) -> NotificationExceptionMode {
let apply:([PeerId : NotificationExceptionWrapper], PeerId, PeerMessageSound) -> [PeerId : NotificationExceptionWrapper] = { values, peerId, sound in
var values = values
if let value = values[peerId] {
switch sound {
case .default:
switch value.settings.muteState {
case .default:
values.removeValue(forKey: peerId)
default:
values[peerId] = value.updateSettings({$0.withUpdatedMessageSound(sound)})
}
default:
values[peerId] = value.updateSettings({$0.withUpdatedMessageSound(sound)})
}
} else {
switch sound {
case .default:
break
default:
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .default, messageSound: sound), date: Date().timeIntervalSince1970)
}
}
return values
}
switch self {
case let .groups(values):
if peerId.namespace != Namespaces.Peer.CloudUser {
return .groups(apply(values, peerId, sound))
}
case let .users(values):
if peerId.namespace == Namespaces.Peer.CloudUser {
return .users(apply(values, peerId, sound))
}
}
return self
}
func withUpdatedPeerIdMuteInterval(_ peerId: PeerId, _ muteInterval: Int32?) -> NotificationExceptionMode {
let apply:([PeerId : NotificationExceptionWrapper], PeerId, PeerMuteState) -> [PeerId : NotificationExceptionWrapper] = { values, peerId, muteState in
var values = values
if let value = values[peerId] {
switch muteState {
case .default:
switch value.settings.messageSound {
case .default:
values.removeValue(forKey: peerId)
default:
values[peerId] = value.updateSettings({$0.withUpdatedMuteState(muteState)})
}
default:
values[peerId] = value.updateSettings({$0.withUpdatedMuteState(muteState)})
}
} else {
switch muteState {
case .default:
break
default:
values[peerId] = NotificationExceptionWrapper.init(settings: TelegramPeerNotificationSettings(muteState: muteState, messageSound: .default), date: Date().timeIntervalSince1970)
}
}
return values
}
let muteState: PeerMuteState
if let muteInterval = muteInterval {
if muteInterval == 0 {
muteState = .unmuted
} else {
let absoluteUntil: Int32
if muteInterval == Int32.max {
absoluteUntil = Int32.max
} else {
absoluteUntil = Int32(Date().timeIntervalSince1970) + muteInterval
}
muteState = .muted(until: absoluteUntil)
}
} else {
muteState = .default
}
switch self {
case let .groups(values):
if peerId.namespace != Namespaces.Peer.CloudUser {
return .groups(apply(values, peerId, muteState))
}
case let .users(values):
if peerId.namespace == Namespaces.Peer.CloudUser {
return .users(apply(values, peerId, muteState))
}
}
return self
}
var peerIds: [PeerId] {
switch self {
case let .users(settings), let .groups(settings):
return settings.map {$0.key}
}
}
var settings: [PeerId : NotificationExceptionWrapper] {
switch self {
case let .users(settings), let .groups(settings):
return settings
}
}
}
private func notificationsExceptionEntries(presentationData: PresentationData, peers: [PeerId : Peer], state: NotificationExceptionState) -> [NotificationExceptionEntry] {
var entries: [NotificationExceptionEntry] = []
entries.append(.search(presentationData.theme, presentationData.strings))
var index: Int = 0
for (key, value) in state.mode.settings.sorted(by: { lhs, rhs in
let lhsName = peers[lhs.key]?.displayTitle ?? ""
let rhsName = peers[rhs.key]?.displayTitle ?? ""
if let lhsDate = lhs.value.date, let rhsDate = rhs.value.date {
return lhsDate < rhsDate
} else if lhs.value.date != nil && rhs.value.date == nil {
return true
} else if lhs.value.date == nil && rhs.value.date != nil {
return false
}
if let lhsPeer = peers[lhs.key] as? TelegramUser, let rhsPeer = peers[rhs.key] as? TelegramUser {
if lhsPeer.botInfo != nil && rhsPeer.botInfo == nil {
return false
} else if lhsPeer.botInfo == nil && rhsPeer.botInfo != nil {
return true
}
}
return lhsName < rhsName
}) {
if let peer = peers[key], !peer.displayTitle.isEmpty {
var title: String
switch value.settings.muteState {
case .muted:
title = presentationData.strings.Notifications_ExceptionsMuted
case .unmuted:
title = presentationData.strings.Notifications_ExceptionsUnmuted
default:
title = ""
}
switch value.settings.messageSound {
case .default:
break
default:
title += (title.isEmpty ? "" : ", ") + localizedPeerNotificationSoundString(strings: presentationData.strings, sound: value.settings.messageSound)
}
entries.append(.peer(index, peer, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, title, value.settings))
index += 1
}
}
return entries
}
public func notificationExceptionsController(account: Account, mode: NotificationExceptionMode, updatedMode:@escaping(NotificationExceptionMode) -> Void) -> ViewController {
let statePromise = ValuePromise(NotificationExceptionState(mode: mode), ignoreRepeated: true)
let stateValue = Atomic(value: NotificationExceptionState(mode: mode))
let updateState: ((NotificationExceptionState) -> NotificationExceptionState) -> Void = { f in
let result = stateValue.modify { f($0) }
statePromise.set(result)
updatedMode(result.mode)
}
let globalValue: Atomic<GlobalNotificationSettingsSet> = Atomic(value: GlobalNotificationSettingsSet.defaultSettings)
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
let presentationData = account.telegramApplicationContext.currentPresentationData.modify {$0}
let updatePeerSound: (PeerId, PeerMessageSound) -> Void = { peerId, sound in
_ = updatePeerNotificationSoundInteractive(account: account, peerId: peerId, sound: sound).start(completed: {
updateState { value in
return value.withUpdatedPeerIdSound(peerId, sound)
}
})
}
let updatePeerNotificationInterval:(PeerId, Int32?) -> Void = { peerId, muteInterval in
_ = updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: muteInterval).start(completed: {
updateState { value in
return value.withUpdatedPeerIdMuteInterval(peerId, muteInterval)
}
})
}
var activateSearch:(()->Void)?
let arguments = NotificationExceptionArguments(account: account, activateSearch: {
activateSearch?()
}, changeNotifications: { peerId, settings in
let globalSettings = globalValue.modify {$0}
let isPrivateChat = peerId.namespace == Namespaces.Peer.CloudUser
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: isPrivateChat && globalSettings.privateChats.enabled || !isPrivateChat && globalSettings.groupChats.enabled ? presentationData.strings.UserInfo_NotificationsDefaultEnabled : presentationData.strings.UserInfo_NotificationsDefaultDisabled, color: .accent, action: { [weak actionSheet] in
updatePeerNotificationInterval(peerId, nil)
actionSheet?.dismissAnimated()
}),
ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsEnable, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
updatePeerNotificationInterval(peerId, 0)
}),
ActionSheetButtonItem(title: presentationData.strings.Notification_Mute1h, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
updatePeerNotificationInterval(peerId, 60 * 60)
}),
ActionSheetButtonItem(title: presentationData.strings.MuteFor_Days(2), color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
updatePeerNotificationInterval(peerId, 60 * 60 * 24 * 2)
}),
ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsDisable, color: .accent, action: { [weak actionSheet] in
updatePeerNotificationInterval(peerId, Int32.max)
actionSheet?.dismissAnimated()
}),
ActionSheetButtonItem(title: presentationData.strings.Notifications_ExceptionsChangeSound(localizedPeerNotificationSoundString(strings: presentationData.strings, sound: settings.messageSound)).0, color: .accent, action: { [weak actionSheet] in
let controller = notificationSoundSelectionController(account: account, isModal: true, currentSound: settings.messageSound, defaultSound: isPrivateChat ? globalSettings.privateChats.sound : globalSettings.groupChats.sound, completion: { value in
updatePeerSound(peerId, value)
})
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
actionSheet?.dismissAnimated()
})
]), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
presentControllerImpl?(actionSheet, nil)
}, selectPeer: {
let filter: ChatListNodePeersFilter
switch mode {
case .groups:
filter = [.withoutSecretChats]
case .users:
filter = [.withoutSecretChats]
}
let controller = PeerSelectionController(account: account, filter: filter, title: presentationData.strings.Notifications_AddExceptionTitle)
controller.peerSelected = { [weak controller] peerId in
controller?.dismiss()
let settingsSignal = account.postbox.transaction { transaction in
return transaction.getPeerNotificationSettings(peerId)
} |> deliverOnMainQueue
_ = settingsSignal.start(next: { settings in
if let settings = settings as? TelegramPeerNotificationSettings {
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
var items: [ActionSheetButtonItem] = []
switch settings.muteState {
case .default, .muted:
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsEnable, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
updatePeerNotificationInterval(peerId, 0)
}))
default:
break
}
items.append(ActionSheetButtonItem(title: presentationData.strings.Notification_Mute1h, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
updatePeerNotificationInterval(peerId, 60 * 60)
}))
items.append(ActionSheetButtonItem(title: presentationData.strings.MuteFor_Days(2), color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
updatePeerNotificationInterval(peerId, 60 * 60 * 24 * 2)
}))
switch settings.muteState {
case .default, .unmuted:
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsDisable, color: .accent, action: { [weak actionSheet] in
updatePeerNotificationInterval(peerId, Int32.max)
actionSheet?.dismissAnimated()
}))
default:
break
}
items.append(ActionSheetButtonItem(title: presentationData.strings.Notifications_ExceptionsChangeSound(localizedPeerNotificationSoundString(strings: presentationData.strings, sound: settings.messageSound)).0, color: .accent, action: { [weak actionSheet] in
let controller = notificationSoundSelectionController(account: account, isModal: true, currentSound: settings.messageSound, defaultSound: nil, completion: { value in
updatePeerSound(peerId, value)
})
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
actionSheet?.dismissAnimated()
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
presentControllerImpl?(actionSheet, nil)
}
})
}
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})
let peersSignal:Signal<[PeerId : Peer], NoError> = statePromise.get() |> mapToSignal { state in
return account.postbox.transaction { transaction -> [PeerId : Peer] in
var peers:[PeerId : Peer] = [:]
for peerId in state.mode.peerIds {
if let peer = transaction.getPeer(peerId) {
peers[peerId] = peer
}
}
return peers
}
}
let preferences = account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications])
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), peersSignal, preferences)
|> map { presentationData, state, peers, prefs -> (ItemListControllerState, (ItemListNodeState<NotificationExceptionEntry>, NotificationExceptionEntry.ItemGenerationArguments)) in
_ = globalValue.swap((prefs.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings)?.effective ?? GlobalNotificationSettingsSet.defaultSettings)
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Notifications_ExceptionsTitle), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: nil)
let listState = ItemListNodeState(entries: notificationsExceptionEntries(presentationData: presentationData, peers: peers, state: state), style: .plain, searchItem: nil)
return (controllerState, (listState, arguments))
}
let controller = NotificationExceptionsController(account: account, state: signal, addAction: {
arguments.selectPeer()
})
// let controller = ItemListController(account: account, state: signal |> afterDisposed {
// actionsDisposable.dispose()
// })
activateSearch = { [weak controller] in
// updateState { state in
// return state.withUpdatedSearchMode(true)
// }
controller?.activateSearch()
}
presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}
return controller
}
private final class NotificationExceptionsController: ViewController {
private let account: Account
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
var peerSelected: ((PeerId) -> Void)?
var inProgress: Bool = false {
didSet {
if self.inProgress != oldValue {
if self.isNodeLoaded {
self.controllerNode.inProgress = self.inProgress
}
if self.inProgress {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(theme: self.presentationData.theme))
} else {
self.navigationItem.rightBarButtonItem = nil
}
}
}
}
private var controllerNode: NotificationExceptionsControllerNode {
return super.displayNode as! NotificationExceptionsControllerNode
}
private let _ready = Promise<Bool>()
override public var ready: Promise<Bool> {
return self._ready
}
private let addAction:()->Void
private let state: Signal<(ItemListControllerState, (ItemListNodeState<NotificationExceptionEntry>, NotificationExceptionEntry.ItemGenerationArguments)), NoError>
public init(account: Account, state: Signal<(ItemListControllerState, (ItemListNodeState<NotificationExceptionEntry>, NotificationExceptionEntry.ItemGenerationArguments)), NoError>, addAction: @escaping()->Void) {
self.account = account
self.state = state
self.addAction = addAction
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.title = self.presentationData.strings.Notifications_ExceptionsTitle
self.scrollToTop = { [weak self] in
if let strongSelf = self {
strongSelf.controllerNode.scrollToTop()
}
}
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
@objc private func addExceptionAction() {
self.addAction()
}
override public func loadDisplayNode() {
let image = PresentationResourcesRootController.navigationAddIcon(presentationData.theme)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: image, style: UIBarButtonItem.Style.plain, target: self, action: #selector(addExceptionAction))
let nodeState = self.state |> deliverOnMainQueue |> map { ($0.theme, $1) }
self.displayNode = NotificationExceptionsControllerNode(account: self.account, navigationBar: self.navigationBar!, state: nodeState)
self.displayNode.backgroundColor = .white
self.controllerNode.navigationBar = self.navigationBar
self.controllerNode.requestDeactivateSearch = { [weak self] in
self?.deactivateSearch()
}
self.controllerNode.requestActivateSearch = { [weak self] in
self?.activateSearch()
}
self.displayNodeDidLoad()
self._ready.set(self.controllerNode.ready)
}
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// self.controllerNode.animateIn()
}
override public func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
}
@objc func cancelPressed() {
self.dismiss()
}
func activateSearch() {
if self.displayNavigationBar {
if let scrollToTop = self.scrollToTop {
scrollToTop()
}
self.controllerNode.activateSearch()
self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
}
}
private func deactivateSearch() {
if !self.displayNavigationBar {
self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring))
self.controllerNode.deactivateSearch()
}
}
}
private final class NotificationExceptionsControllerNode: ASDisplayNode {
private let account: Account
var inProgress: Bool = false {
didSet {
}
}
var navigationBar: NavigationBar?
private let contentNode: ItemListControllerNode<NotificationExceptionEntry>
private var contactListActive = false
private var searchDisplayController: SearchDisplayController?
private var containerLayout: (ContainerViewLayout, CGFloat)?
var requestActivateSearch: (() -> Void)?
var requestDeactivateSearch: (() -> Void)?
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private var readyValue = Promise<Bool>()
var ready: Signal<Bool, NoError> {
return self.readyValue.get()
}
private let state: Signal<(PresentationTheme, (ItemListNodeState<NotificationExceptionEntry>, NotificationExceptionEntry.ItemGenerationArguments)), NoError>
init(account: Account, navigationBar: NavigationBar, state: Signal<(PresentationTheme, (ItemListNodeState<NotificationExceptionEntry>, NotificationExceptionEntry.ItemGenerationArguments)), NoError>) {
self.account = account
self.state = state
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
self.contentNode = ItemListControllerNode(navigationBar: navigationBar, updateNavigationOffset: { _ in
}, state: state)
super.init()
self.setViewBlock({
return UITracingLayerView()
})
self.addSubnode(self.contentNode)
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
strongSelf.updateThemeAndStrings()
}
}
})
self.readyValue.set(contentNode.ready)
}
deinit {
self.presentationDataDisposable?.dispose()
}
private func updateThemeAndStrings() {
self.searchDisplayController?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.containerLayout = (layout, navigationBarHeight)
let cleanInsets = layout.insets(options: [])
var controlSize = CGSize(width: 0, height:0)
controlSize.width = min(layout.size.width, max(200.0, controlSize.width))
var insets = layout.insets(options: [.input])
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
insets.bottom = max(insets.bottom, cleanInsets.bottom)
insets.left += layout.safeInsets.left
insets.right += layout.safeInsets.right
self.contentNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
self.contentNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
self.contentNode.containerLayoutUpdated(layout, navigationBarHeight: insets.top, transition: transition)
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
}
}
func activateSearch() {
guard let (containerLayout, navigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar else {
return
}
if self.contentNode.supernode != nil {
var maybePlaceholderNode: SearchBarPlaceholderNode?
self.contentNode.listNode.forEachItemNode { node in
if let node = node as? NotificationSearchItemNode {
maybePlaceholderNode = node.searchBarNode
}
}
if let _ = self.searchDisplayController {
return
}
if let placeholderNode = maybePlaceholderNode {
self.searchDisplayController = SearchDisplayController(theme: self.presentationData.theme, strings: self.presentationData.strings, contentNode: NotificationExceptionsSearchControllerContentNode(account: account, navigationBar: navigationBar, state: self.state), cancel: { [weak self] in
self?.requestDeactivateSearch?()
})
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { subnode in
self.insertSubnode(subnode, belowSubnode: navigationBar)
}, placeholder: placeholderNode)
}
}
}
func deactivateSearch() {
if let searchDisplayController = self.searchDisplayController {
if self.contentNode.supernode != nil {
var maybePlaceholderNode: SearchBarPlaceholderNode?
self.contentNode.listNode.forEachItemNode { node in
if let node = node as? NotificationSearchItemNode {
maybePlaceholderNode = node.searchBarNode
}
}
searchDisplayController.deactivate(placeholder: maybePlaceholderNode)
self.searchDisplayController = nil
}
}
}
func scrollToTop() {
if self.contentNode.supernode != nil {
// self.contentNode.scrollToPosition(.top)
}
}
}
final class NotificationExceptionsSearchControllerContentNode: SearchDisplayControllerContentNode {
private let account: Account
private let listNode: ItemListControllerNode<NotificationExceptionEntry>
private let dimNode: ASDisplayNode
private var validLayout: ContainerViewLayout?
private let searchQuery = Promise<String?>()
private let searchDisposable = MetaDisposable()
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private let presentationDataPromise: Promise<ChatListPresentationData>
private let _isSearching = ValuePromise<Bool>(false, ignoreRepeated: true)
override var isSearching: Signal<Bool, NoError> {
return self._isSearching.get()
}
private let state: Signal<(PresentationTheme, (ItemListNodeState<NotificationExceptionEntry>, NotificationExceptionEntry.ItemGenerationArguments)), NoError>
init(account: Account, navigationBar: NavigationBar, state: Signal<(PresentationTheme, (ItemListNodeState<NotificationExceptionEntry>, NotificationExceptionEntry.ItemGenerationArguments)), NoError>) {
self.account = account
self.state = state
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
self.presentationDataPromise = Promise(ChatListPresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations))
self.listNode = ItemListControllerNode(navigationBar: navigationBar, updateNavigationOffset: { _ in
}, state: searchQuery.get() |> mapToSignal { query in
return state |> map { values in
var values = values
let entries = values.1.0.entries.filter { entry in
switch entry {
case .search:
return false
case let .peer(_, peer, _, _, _, _, _):
if let query = query {
return !peer.displayTitle.components(separatedBy: " ").filter({$0.lowercased().hasPrefix(query.lowercased())}).isEmpty && !query.isEmpty
} else {
return false
}
}
}
values.1.0 = ItemListNodeState(entries: entries, style: values.1.0.style, focusItemTag: nil, emptyStateItem: nil, searchItem: nil, crossfadeState: false, animateChanges: false)
return values
}
})
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5)
super.init()
self.addSubnode(self.dimNode)
self.addSubnode(self.listNode)
self.listNode.isHidden = true
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme {
strongSelf.updateTheme(theme: presentationData.theme)
}
}
})
}
deinit {
self.searchDisposable.dispose()
self.presentationDataDisposable?.dispose()
}
private func updateTheme(theme: PresentationTheme) {
self.backgroundColor = theme.chatList.backgroundColor
}
override func didLoad() {
super.didLoad()
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
}
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.cancel?()
}
}
override func searchTextUpdated(text: String) {
if text.isEmpty {
self.searchQuery.set(.single(nil))
self.listNode.isHidden = true
} else {
self.searchQuery.set(.single(text))
self.listNode.isHidden = false
}
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
self.validLayout = layout
let topInset = navigationBarHeight
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)))
self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size)
self.listNode.containerLayoutUpdated(layout, navigationBarHeight: 28, transition: transition)
}
}

View File

@ -0,0 +1,130 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Postbox
import Display
import SwiftSignalKit
private let searchBarFont = Font.regular(14.0)
class NotificationSearchItem: ListViewItem, ItemListItem {
let selectable: Bool = false
var sectionId: ItemListSectionId {
return 0
}
var tag: ItemListItemTag? {
return nil
}
var requestsNoInset: Bool {
return true
}
let theme: PresentationTheme
private let placeholder: String
private let activate: () -> Void
init(theme: PresentationTheme, placeholder: String, activate: @escaping () -> Void) {
self.theme = theme
self.placeholder = placeholder
self.activate = activate
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
async {
let node = NotificationSearchItemNode()
node.placeholder = self.placeholder
let makeLayout = node.asyncLayout()
let (layout, apply) = makeLayout(self, params)
node.contentSize = layout.contentSize
node.insets = layout.insets
node.activate = self.activate
Queue.mainQueue().async {
completion(node, {
return (nil, {
apply(false)
})
})
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? NotificationSearchItemNode {
let layout = nodeValue.asyncLayout()
async {
let (nodeLayout, apply) = layout(self, params)
Queue.mainQueue().async {
completion(nodeLayout, {
apply(animation.isAnimated)
})
}
}
}
}
}
}
class NotificationSearchItemNode: ListViewItemNode {
let searchBarNode: SearchBarPlaceholderNode
var placeholder: String?
fileprivate var activate: (() -> Void)? {
didSet {
self.searchBarNode.activate = self.activate
}
}
required init() {
self.searchBarNode = SearchBarPlaceholderNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.searchBarNode)
}
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
let makeLayout = self.asyncLayout()
let (layout, apply) = makeLayout(item as! NotificationSearchItem, params)
apply(false)
self.contentSize = layout.contentSize
self.insets = layout.insets
}
func asyncLayout() -> (_ item: NotificationSearchItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let searchBarNodeLayout = self.searchBarNode.asyncLayout()
let placeholder = self.placeholder
return { item, params in
let baseWidth = params.width - params.leftInset - params.rightInset
let backgroundColor = item.theme.chatList.itemBackgroundColor
let searchBarApply = searchBarNodeLayout(NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: UIColor(rgb: 0x8e8e93)), CGSize(width: baseWidth - 16.0, height: CGFloat.greatestFiniteMagnitude), UIColor(rgb: 0x8e8e93), item.theme.chatList.regularSearchBarColor, backgroundColor)
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 44.0), insets: UIEdgeInsets())
return (layout, { [weak self] animated in
if let strongSelf = self {
let transition: ContainedViewLayoutTransition
if animated {
transition = .animated(duration: 0.3, curve: .easeInOut)
} else {
transition = .immediate
}
strongSelf.searchBarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 8.0, y: 8.0), size: CGSize(width: baseWidth - 16.0, height: 28.0))
searchBarApply()
strongSelf.searchBarNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: baseWidth - 16.0, height: 28.0))
transition.updateBackgroundColor(node: strongSelf, color: backgroundColor)
}
})
}
}
}

View File

@ -6,7 +6,8 @@ import TelegramCore
private final class NotificationsAndSoundsArguments { private final class NotificationsAndSoundsArguments {
let account: Account let account: Account
let presentController: (ViewController, ViewControllerPresentationArguments) -> Void let presentController: (ViewController, ViewControllerPresentationArguments?) -> Void
let pushController: (ViewController) -> Void
let soundSelectionDisposable: MetaDisposable let soundSelectionDisposable: MetaDisposable
let updateMessageAlerts: (Bool) -> Void let updateMessageAlerts: (Bool) -> Void
@ -27,9 +28,12 @@ private final class NotificationsAndSoundsArguments {
let resetNotifications: () -> Void let resetNotifications: () -> Void
init(account: Account, presentController: @escaping (ViewController, ViewControllerPresentationArguments) -> Void, soundSelectionDisposable: MetaDisposable, updateMessageAlerts: @escaping (Bool) -> Void, updateMessagePreviews: @escaping (Bool) -> Void, updateMessageSound: @escaping (PeerMessageSound) -> Void, updateGroupAlerts: @escaping (Bool) -> Void, updateGroupPreviews: @escaping (Bool) -> Void, updateGroupSound: @escaping (PeerMessageSound) -> Void, updateInAppSounds: @escaping (Bool) -> Void, updateInAppVibration: @escaping (Bool) -> Void, updateInAppPreviews: @escaping (Bool) -> Void, updateDisplayNameOnLockscreen: @escaping (Bool) -> Void, updateTotalUnreadCountStyle: @escaping (Bool) -> Void, updateTotalUnreadCountCategory: @escaping (Bool) -> Void, resetNotifications: @escaping () -> Void) { let updatedExceptionMode: (NotificationExceptionMode) -> Void
init(account: Account, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, updateMessageAlerts: @escaping (Bool) -> Void, updateMessagePreviews: @escaping (Bool) -> Void, updateMessageSound: @escaping (PeerMessageSound) -> Void, updateGroupAlerts: @escaping (Bool) -> Void, updateGroupPreviews: @escaping (Bool) -> Void, updateGroupSound: @escaping (PeerMessageSound) -> Void, updateInAppSounds: @escaping (Bool) -> Void, updateInAppVibration: @escaping (Bool) -> Void, updateInAppPreviews: @escaping (Bool) -> Void, updateDisplayNameOnLockscreen: @escaping (Bool) -> Void, updateTotalUnreadCountStyle: @escaping (Bool) -> Void, updateTotalUnreadCountCategory: @escaping (Bool) -> Void, resetNotifications: @escaping () -> Void, updatedExceptionMode: @escaping(NotificationExceptionMode) -> Void) {
self.account = account self.account = account
self.presentController = presentController self.presentController = presentController
self.pushController = pushController
self.soundSelectionDisposable = soundSelectionDisposable self.soundSelectionDisposable = soundSelectionDisposable
self.updateMessageAlerts = updateMessageAlerts self.updateMessageAlerts = updateMessageAlerts
self.updateMessagePreviews = updateMessagePreviews self.updateMessagePreviews = updateMessagePreviews
@ -44,6 +48,7 @@ private final class NotificationsAndSoundsArguments {
self.updateTotalUnreadCountStyle = updateTotalUnreadCountStyle self.updateTotalUnreadCountStyle = updateTotalUnreadCountStyle
self.updateTotalUnreadCountCategory = updateTotalUnreadCountCategory self.updateTotalUnreadCountCategory = updateTotalUnreadCountCategory
self.resetNotifications = resetNotifications self.resetNotifications = resetNotifications
self.updatedExceptionMode = updatedExceptionMode
} }
} }
@ -61,12 +66,15 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
case messageAlerts(PresentationTheme, String, Bool) case messageAlerts(PresentationTheme, String, Bool)
case messagePreviews(PresentationTheme, String, Bool) case messagePreviews(PresentationTheme, String, Bool)
case messageSound(PresentationTheme, String, String, PeerMessageSound) case messageSound(PresentationTheme, String, String, PeerMessageSound)
case userExceptions(PresentationTheme, PresentationStrings, String, NotificationExceptionMode)
case messageNotice(PresentationTheme, String) case messageNotice(PresentationTheme, String)
case groupHeader(PresentationTheme, String) case groupHeader(PresentationTheme, String)
case groupAlerts(PresentationTheme, String, Bool) case groupAlerts(PresentationTheme, String, Bool)
case groupPreviews(PresentationTheme, String, Bool) case groupPreviews(PresentationTheme, String, Bool)
case groupSound(PresentationTheme, String, String, PeerMessageSound) case groupSound(PresentationTheme, String, String, PeerMessageSound)
case groupExceptions(PresentationTheme, PresentationStrings, String, NotificationExceptionMode)
case groupNotice(PresentationTheme, String) case groupNotice(PresentationTheme, String)
case inAppHeader(PresentationTheme, String) case inAppHeader(PresentationTheme, String)
@ -87,9 +95,9 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .messageHeader, .messageAlerts, .messagePreviews, .messageSound, .messageNotice: case .messageHeader, .messageAlerts, .messagePreviews, .messageSound, .messageNotice, .userExceptions:
return NotificationsAndSoundsSection.messages.rawValue return NotificationsAndSoundsSection.messages.rawValue
case .groupHeader, .groupAlerts, .groupPreviews, .groupSound, .groupNotice: case .groupHeader, .groupAlerts, .groupPreviews, .groupSound, .groupNotice, .groupExceptions:
return NotificationsAndSoundsSection.groups.rawValue return NotificationsAndSoundsSection.groups.rawValue
case .inAppHeader, .inAppSounds, .inAppVibrate, .inAppPreviews: case .inAppHeader, .inAppSounds, .inAppVibrate, .inAppPreviews:
return NotificationsAndSoundsSection.inApp.rawValue return NotificationsAndSoundsSection.inApp.rawValue
@ -112,42 +120,46 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
return 2 return 2
case .messageSound: case .messageSound:
return 3 return 3
case .messageNotice: case .userExceptions:
return 4 return 4
case .groupHeader: case .messageNotice:
return 5 return 5
case .groupAlerts: case .groupHeader:
return 6 return 6
case .groupPreviews: case .groupAlerts:
return 7 return 7
case .groupSound: case .groupPreviews:
return 8 return 8
case .groupNotice: case .groupSound:
return 9 return 9
case .inAppHeader: case .groupExceptions:
return 10 return 10
case .inAppSounds: case .groupNotice:
return 11 return 11
case .inAppVibrate: case .inAppHeader:
return 12 return 12
case .inAppPreviews: case .inAppSounds:
return 13 return 13
case .displayNamesOnLockscreen: case .inAppVibrate:
return 14 return 14
case .displayNamesOnLockscreenInfo: case .inAppPreviews:
return 15 return 15
case .badgeHeader: case .displayNamesOnLockscreen:
return 16 return 16
case .unreadCountStyle: case .displayNamesOnLockscreenInfo:
return 17 return 17
case .unreadCountCategory: case .badgeHeader:
return 18 return 18
case .unreadCountCategoryInfo: case .unreadCountStyle:
return 19 return 19
case .reset: case .unreadCountCategory:
return 20 return 20
case .resetNotice: case .unreadCountCategoryInfo:
return 21 return 21
case .reset:
return 22
case .resetNotice:
return 23
} }
} }
@ -177,6 +189,12 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .userExceptions(lhsTheme, lhsStrings, lhsText, lhsValue):
if case let .userExceptions(rhsTheme, rhsStrings, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .messageNotice(lhsTheme, lhsText): case let .messageNotice(lhsTheme, lhsText):
if case let .messageNotice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .messageNotice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
@ -207,6 +225,12 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .groupExceptions(lhsTheme, lhsStrings, lhsText, lhsValue):
if case let .groupExceptions(rhsTheme, rhsStrings, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .groupNotice(lhsTheme, lhsText): case let .groupNotice(lhsTheme, lhsText):
if case let .groupNotice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .groupNotice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
@ -309,7 +333,12 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
let controller = notificationSoundSelectionController(account: arguments.account, isModal: true, currentSound: sound, defaultSound: nil, completion: { [weak arguments] value in let controller = notificationSoundSelectionController(account: arguments.account, isModal: true, currentSound: sound, defaultSound: nil, completion: { [weak arguments] value in
arguments?.updateMessageSound(value) arguments?.updateMessageSound(value)
}) })
arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) arguments.presentController(controller, nil)
})
case let .userExceptions(theme, strings, text, value):
return ItemListDisclosureItem(theme: theme, title: text, label: strings.Notifications_Exceptions(Int32(value.settings.count)), sectionId: self.section, style: .blocks, action: {
let controller = notificationExceptionsController(account: arguments.account, mode: value, updatedMode: arguments.updatedExceptionMode)
arguments.pushController(controller)
}) })
case let .messageNotice(theme, text): case let .messageNotice(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
@ -330,6 +359,11 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
}) })
arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}) })
case let .groupExceptions(theme, strings, text, value):
return ItemListDisclosureItem(theme: theme, title: text, label: strings.Notifications_Exceptions(Int32(value.settings.count)), sectionId: self.section, style: .blocks, action: {
let controller = notificationExceptionsController(account: arguments.account, mode: value, updatedMode: arguments.updatedExceptionMode)
arguments.pushController(controller)
})
case let .groupNotice(theme, text): case let .groupNotice(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .inAppHeader(theme, text): case let .inAppHeader(theme, text):
@ -382,19 +416,21 @@ private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound
} }
} }
private func notificationsAndSoundsEntries(globalSettings: GlobalNotificationSettingsSet, inAppSettings: InAppNotificationSettings, presentationData: PresentationData) -> [NotificationsAndSoundsEntry] { private func notificationsAndSoundsEntries(globalSettings: GlobalNotificationSettingsSet, inAppSettings: InAppNotificationSettings, exceptions: (NotificationExceptionMode, NotificationExceptionMode), presentationData: PresentationData) -> [NotificationsAndSoundsEntry] {
var entries: [NotificationsAndSoundsEntry] = [] var entries: [NotificationsAndSoundsEntry] = []
entries.append(.messageHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications)) entries.append(.messageHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications))
entries.append(.messageAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.privateChats.enabled)) entries.append(.messageAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.privateChats.enabled))
entries.append(.messagePreviews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, globalSettings.privateChats.displayPreviews)) entries.append(.messagePreviews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, globalSettings.privateChats.displayPreviews))
entries.append(.messageSound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, sound: filteredGlobalSound(globalSettings.privateChats.sound)), filteredGlobalSound(globalSettings.privateChats.sound))) entries.append(.messageSound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, sound: filteredGlobalSound(globalSettings.privateChats.sound)), filteredGlobalSound(globalSettings.privateChats.sound)))
entries.append(.userExceptions(presentationData.theme, presentationData.strings, presentationData.strings.Notifications_MessageNotificationsExceptions, exceptions.0))
entries.append(.messageNotice(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsHelp)) entries.append(.messageNotice(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsHelp))
entries.append(.groupHeader(presentationData.theme, presentationData.strings.Notifications_GroupNotifications)) entries.append(.groupHeader(presentationData.theme, presentationData.strings.Notifications_GroupNotifications))
entries.append(.groupAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.groupChats.enabled)) entries.append(.groupAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.groupChats.enabled))
entries.append(.groupPreviews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, globalSettings.groupChats.displayPreviews)) entries.append(.groupPreviews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, globalSettings.groupChats.displayPreviews))
entries.append(.groupSound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, sound: filteredGlobalSound(globalSettings.groupChats.sound)), filteredGlobalSound(globalSettings.groupChats.sound))) entries.append(.groupSound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, sound: filteredGlobalSound(globalSettings.groupChats.sound)), filteredGlobalSound(globalSettings.groupChats.sound)))
entries.append(.groupExceptions(presentationData.theme, presentationData.strings, presentationData.strings.Notifications_MessageNotificationsExceptions, exceptions.1))
entries.append(.groupNotice(presentationData.theme, presentationData.strings.Notifications_GroupNotificationsHelp)) entries.append(.groupNotice(presentationData.theme, presentationData.strings.Notifications_GroupNotificationsHelp))
entries.append(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications)) entries.append(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications))
@ -418,9 +454,20 @@ private func notificationsAndSoundsEntries(globalSettings: GlobalNotificationSet
public func notificationsAndSoundsController(account: Account) -> ViewController { public func notificationsAndSoundsController(account: Account) -> ViewController {
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
let notificationExceptions: Promise<(NotificationExceptionMode, NotificationExceptionMode)> = Promise()
let updateNotificationExceptions:((NotificationExceptionMode, NotificationExceptionMode)) -> Void = { value in
notificationExceptions.set(.single(value))
}
let arguments = NotificationsAndSoundsArguments(account: account, presentController: { controller, arguments in let arguments = NotificationsAndSoundsArguments(account: account, presentController: { controller, arguments in
presentControllerImpl?(controller, arguments) presentControllerImpl?(controller, arguments)
}, pushController: { controller in
pushControllerImpl?(controller)
}, soundSelectionDisposable: MetaDisposable(), updateMessageAlerts: { value in }, soundSelectionDisposable: MetaDisposable(), updateMessageAlerts: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: account.postbox, { settings in let _ = updateGlobalNotificationSettingsInteractively(postbox: account.postbox, { settings in
return settings.withUpdatedPrivateChats { return settings.withUpdatedPrivateChats {
@ -516,12 +563,57 @@ public func notificationsAndSoundsController(account: Account) -> ViewController
}) })
])]) ])])
presentControllerImpl?(actionSheet, nil) presentControllerImpl?(actionSheet, nil)
}, updatedExceptionMode: { mode in
_ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups) in
switch mode {
case .users:
updateNotificationExceptions((mode, groups))
case .groups:
updateNotificationExceptions((users, mode))
}
})
}) })
let preferences = account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications, ApplicationSpecificPreferencesKeys.inAppNotificationSettings]) let preferences = account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications, ApplicationSpecificPreferencesKeys.inAppNotificationSettings])
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, preferences)
|> map { presentationData, view -> (ItemListControllerState, (ItemListNodeState<NotificationsAndSoundsEntry>, NotificationsAndSoundsEntry.ItemGenerationArguments)) in
notificationExceptions.set(account.postbox.transaction{ transaction -> (NotificationExceptionMode, NotificationExceptionMode) in
let allSettings = transaction.getAllPeerNotificationSettings() ?? [:]
var users:[PeerId : NotificationExceptionWrapper] = [:]
var groups: [PeerId : NotificationExceptionWrapper] = [:]
for (key, value) in allSettings {
if let value = value as? TelegramPeerNotificationSettings {
switch value.muteState {
case .default:
switch value.messageSound {
case .default:
break
default:
switch key.namespace {
case Namespaces.Peer.CloudUser:
users[key] = NotificationExceptionWrapper(settings: value)
default:
groups[key] = NotificationExceptionWrapper(settings: value)
}
}
default:
switch key.namespace {
case Namespaces.Peer.CloudUser:
users[key] = NotificationExceptionWrapper(settings: value)
default:
groups[key] = NotificationExceptionWrapper(settings: value)
}
}
}
}
return (.users(users), .groups(groups))
})
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, preferences, notificationExceptions.get())
|> map { presentationData, view, exceptions -> (ItemListControllerState, (ItemListNodeState<NotificationsAndSoundsEntry>, NotificationsAndSoundsEntry.ItemGenerationArguments)) in
let viewSettings: GlobalNotificationSettingsSet let viewSettings: GlobalNotificationSettingsSet
if let settings = view.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings { if let settings = view.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
@ -538,7 +630,7 @@ public func notificationsAndSoundsController(account: Account) -> ViewController
} }
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Notifications_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Notifications_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(entries: notificationsAndSoundsEntries(globalSettings: viewSettings, inAppSettings: inAppSettings, presentationData: presentationData), style: .blocks) let listState = ItemListNodeState(entries: notificationsAndSoundsEntries(globalSettings: viewSettings, inAppSettings: inAppSettings, exceptions: exceptions, presentationData: presentationData), style: .blocks)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }
@ -547,5 +639,8 @@ public func notificationsAndSoundsController(account: Account) -> ViewController
presentControllerImpl = { [weak controller] c, a in presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a) controller?.present(c, in: .window(.root), with: a)
} }
pushControllerImpl = { [weak controller] c in
(controller?.navigationController as? NavigationController)?.pushViewController(c)
}
return controller return controller
} }

View File

@ -40,7 +40,7 @@ public final class PeerSelectionController: ViewController {
return self._ready return self._ready
} }
public init(account: Account, filter: ChatListNodePeersFilter = [.onlyWriteable]) { public init(account: Account, filter: ChatListNodePeersFilter = [.onlyWriteable], title: String? = nil) {
self.account = account self.account = account
self.filter = filter self.filter = filter
@ -49,7 +49,7 @@ public final class PeerSelectionController: ViewController {
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.title = self.presentationData.strings.Conversation_ForwardTitle self.title = title ?? self.presentationData.strings.Conversation_ForwardTitle
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))

View File

@ -663,11 +663,11 @@ public func chatMessagePhotoInternal(photoData: Signal<(Data?, Data?, Bool), NoE
} }
} }
private func chatMessagePhotoThumbnailDatas(account: Account, photoReference: ImageMediaReference) -> Signal<(Data?, Data?, Bool), NoError> { private func chatMessagePhotoThumbnailDatas(account: Account, photoReference: ImageMediaReference, onlyFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
let fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0) let fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0)
if let smallestRepresentation = smallestImageRepresentation(photoReference.media.representations), let largestRepresentation = photoReference.media.representationForDisplayAtSize(fullRepresentationSize) { if let smallestRepresentation = smallestImageRepresentation(photoReference.media.representations), let largestRepresentation = photoReference.media.representationForDisplayAtSize(fullRepresentationSize) {
let maybeFullSize = account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 180.0, height: 180.0), mode: .aspectFit), complete: false) let maybeFullSize = account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 180.0, height: 180.0), mode: .aspectFit), complete: onlyFullSize)
let signal = maybeFullSize let signal = maybeFullSize
|> take(1) |> take(1)
@ -710,12 +710,12 @@ private func chatMessagePhotoThumbnailDatas(account: Account, photoReference: Im
} }
} }
public func chatMessagePhotoThumbnail(account: Account, photoReference: ImageMediaReference) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { public func chatMessagePhotoThumbnail(account: Account, photoReference: ImageMediaReference, onlyFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = chatMessagePhotoThumbnailDatas(account: account, photoReference: photoReference) let signal = chatMessagePhotoThumbnailDatas(account: account, photoReference: photoReference, onlyFullSize: onlyFullSize)
return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in
return { arguments in return { arguments in
let context = DrawingContext(size: arguments.drawingSize, clear: true) let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true)
let drawingRect = arguments.drawingRect let drawingRect = arguments.drawingRect
var fittedSize = arguments.imageSize var fittedSize = arguments.imageSize
@ -803,7 +803,7 @@ public func chatMessageVideoThumbnail(account: Account, fileReference: FileMedia
return signal return signal
|> map { (thumbnailData, fullSizeData, fullSizeComplete) in |> map { (thumbnailData, fullSizeData, fullSizeComplete) in
return { arguments in return { arguments in
let context = DrawingContext(size: arguments.drawingSize, clear: true) let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true)
let drawingRect = arguments.drawingRect let drawingRect = arguments.drawingRect
var fittedSize = arguments.imageSize var fittedSize = arguments.imageSize
@ -1565,7 +1565,7 @@ func chatWebpageSnippetPhoto(account: Account, photoReference: ImageMediaReferen
} }
if let fullSizeImage = fullSizeImage { if let fullSizeImage = fullSizeImage {
let context = DrawingContext(size: arguments.drawingSize, clear: true) let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true)
let fittedSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height).aspectFilled(arguments.boundingSize) let fittedSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height).aspectFilled(arguments.boundingSize)
let drawingRect = arguments.drawingRect let drawingRect = arguments.drawingRect

View File

@ -17,6 +17,7 @@ private enum ApplicationSpecificPreferencesKeyValues: Int32 {
case experimentalUISettings = 11 case experimentalUISettings = 11
case contactSynchronizationSettings = 12 case contactSynchronizationSettings = 12
case stickerSettings = 13 case stickerSettings = 13
case watchPresetSettings = 14
} }
public struct ApplicationSpecificPreferencesKeys { public struct ApplicationSpecificPreferencesKeys {
@ -34,4 +35,5 @@ public struct ApplicationSpecificPreferencesKeys {
public static let experimentalUISettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.experimentalUISettings.rawValue) public static let experimentalUISettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.experimentalUISettings.rawValue)
public static let contactSynchronizationSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.contactSynchronizationSettings.rawValue) public static let contactSynchronizationSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.contactSynchronizationSettings.rawValue)
public static let stickerSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.stickerSettings.rawValue) public static let stickerSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.stickerSettings.rawValue)
public static let watchPresetSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.watchPresetSettings.rawValue)
} }

View File

@ -17,7 +17,8 @@ private final class SettingsItemIcons {
static let appearance = UIImage(bundleImageName: "Settings/MenuIcons/Appearance")?.precomposed() static let appearance = UIImage(bundleImageName: "Settings/MenuIcons/Appearance")?.precomposed()
static let language = UIImage(bundleImageName: "Settings/MenuIcons/Language")?.precomposed() static let language = UIImage(bundleImageName: "Settings/MenuIcons/Language")?.precomposed()
static let secureId = UIImage(bundleImageName: "Settings/MenuIcons/Passport")?.precomposed() static let passport = UIImage(bundleImageName: "Settings/MenuIcons/Passport")?.precomposed()
static let watch = UIImage(bundleImageName: "Settings/MenuIcons/Watch")?.precomposed()
static let support = UIImage(bundleImageName: "Settings/MenuIcons/Support")?.precomposed() static let support = UIImage(bundleImageName: "Settings/MenuIcons/Support")?.precomposed()
static let faq = UIImage(bundleImageName: "Settings/MenuIcons/Faq")?.precomposed() static let faq = UIImage(bundleImageName: "Settings/MenuIcons/Faq")?.precomposed()
@ -42,6 +43,7 @@ private struct SettingsItemArguments {
let presentController: (ViewController) -> Void let presentController: (ViewController) -> Void
let openLanguage: () -> Void let openLanguage: () -> Void
let openPassport: () -> Void let openPassport: () -> Void
let openWatch: () -> Void
let openSupport: () -> Void let openSupport: () -> Void
let openFaq: () -> Void let openFaq: () -> Void
let openEditing: () -> Void let openEditing: () -> Void
@ -54,7 +56,7 @@ private enum SettingsSection: Int32 {
case proxy case proxy
case media case media
case generalSettings case generalSettings
case passport case advanced
case help case help
} }
@ -75,6 +77,7 @@ private enum SettingsEntry: ItemListNodeEntry {
case themes(PresentationTheme, UIImage?, String) case themes(PresentationTheme, UIImage?, String)
case language(PresentationTheme, UIImage?, String, String) case language(PresentationTheme, UIImage?, String, String)
case passport(PresentationTheme, UIImage?, String, String) case passport(PresentationTheme, UIImage?, String, String)
case watch(PresentationTheme, UIImage?, String, String)
case askAQuestion(PresentationTheme, UIImage?, String) case askAQuestion(PresentationTheme, UIImage?, String)
case faq(PresentationTheme, UIImage?, String) case faq(PresentationTheme, UIImage?, String)
@ -89,8 +92,8 @@ private enum SettingsEntry: ItemListNodeEntry {
return SettingsSection.media.rawValue return SettingsSection.media.rawValue
case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .themes, .language: case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .themes, .language:
return SettingsSection.generalSettings.rawValue return SettingsSection.generalSettings.rawValue
case .passport: case .passport, .watch :
return SettingsSection.passport.rawValue return SettingsSection.advanced.rawValue
case .askAQuestion, .faq: case .askAQuestion, .faq:
return SettingsSection.help.rawValue return SettingsSection.help.rawValue
} }
@ -124,10 +127,12 @@ private enum SettingsEntry: ItemListNodeEntry {
return 11 return 11
case .passport: case .passport:
return 12 return 12
case .askAQuestion: case .watch:
return 13 return 13
case .faq: case .askAQuestion:
return 14 return 14
case .faq:
return 15
} }
} }
@ -240,6 +245,12 @@ private enum SettingsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .watch(lhsTheme, lhsImage, lhsText, lhsValue):
if case let .watch(rhsTheme, rhsImage, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .askAQuestion(lhsTheme, lhsImage, lhsText): case let .askAQuestion(lhsTheme, lhsImage, lhsText):
if case let .askAQuestion(rhsTheme, rhsImage, rhsText) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText { if case let .askAQuestion(rhsTheme, rhsImage, rhsText) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText {
return true return true
@ -320,6 +331,10 @@ private enum SettingsEntry: ItemListNodeEntry {
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: { return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openPassport() arguments.openPassport()
}) })
case let .watch(theme, image, text, value):
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openWatch()
})
case let .askAQuestion(theme, image, text): case let .askAQuestion(theme, image, text):
return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: { return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openSupport() arguments.openSupport()
@ -351,7 +366,7 @@ private struct SettingsState: Equatable {
} }
} }
private func settingsEntries(presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, hasPassport: Bool) -> [SettingsEntry] { private func settingsEntries(presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, hasPassport: Bool, hasWatchApp: Bool) -> [SettingsEntry] {
var entries: [SettingsEntry] = [] var entries: [SettingsEntry] = []
if let peer = peerViewMainPeer(view) as? TelegramUser { if let peer = peerViewMainPeer(view) as? TelegramUser {
@ -390,7 +405,10 @@ private func settingsEntries(presentationData: PresentationData, state: Settings
entries.append(.language(presentationData.theme, SettingsItemIcons.language, presentationData.strings.Settings_AppLanguage, presentationData.strings.Localization_LanguageName)) entries.append(.language(presentationData.theme, SettingsItemIcons.language, presentationData.strings.Settings_AppLanguage, presentationData.strings.Localization_LanguageName))
if hasPassport { if hasPassport {
entries.append(.passport(presentationData.theme, SettingsItemIcons.secureId, presentationData.strings.Settings_Passport, "")) entries.append(.passport(presentationData.theme, SettingsItemIcons.passport, presentationData.strings.Settings_Passport, ""))
}
if hasWatchApp {
entries.append(.watch(presentationData.theme, SettingsItemIcons.watch, presentationData.strings.Settings_AppleWatch, ""))
} }
entries.append(.askAQuestion(presentationData.theme, SettingsItemIcons.support, presentationData.strings.Settings_Support)) entries.append(.askAQuestion(presentationData.theme, SettingsItemIcons.support, presentationData.strings.Settings_Support))
@ -516,6 +534,9 @@ public func settingsController(account: Account, accountManager: AccountManager)
}, openPassport: { }, openPassport: {
let controller = SecureIdAuthController(account: account, mode: .list) let controller = SecureIdAuthController(account: account, mode: .list)
presentControllerImpl?(controller, nil) presentControllerImpl?(controller, nil)
}, openWatch: {
let controller = watchSettingsController(account: account)
pushControllerImpl?(controller)
}, openSupport: { }, openSupport: {
let supportPeer = Promise<PeerId?>() let supportPeer = Promise<PeerId?>()
supportPeer.set(supportPeerId(account: account)) supportPeer.set(supportPeerId(account: account))
@ -654,8 +675,13 @@ public func settingsController(account: Account, accountManager: AccountManager)
} }
updatePassport() updatePassport()
let signal = combineLatest(account.telegramApplicationContext.presentationData, statePromise.get(), peerView, account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), combineLatest(account.viewTracker.featuredStickerPacks(), archivedPacks.get()), hasPassport.get()) let hasWatchApp = Promise<Bool>(false)
|> map { presentationData, state, view, preferences, featuredAndArchived, hasPassport -> (ItemListControllerState, (ItemListNodeState<SettingsEntry>, SettingsEntry.ItemGenerationArguments)) in if let context = account.applicationContext as? TelegramApplicationContext, let watchManager = context.watchManager {
hasWatchApp.set(watchManager.watchAppInstalled)
}
let signal = combineLatest(account.telegramApplicationContext.presentationData, statePromise.get(), peerView, account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), combineLatest(account.viewTracker.featuredStickerPacks(), archivedPacks.get()), combineLatest(hasPassport.get(), hasWatchApp.get()))
|> map { presentationData, state, view, preferences, featuredAndArchived, hasPassportAndWatch -> (ItemListControllerState, (ItemListNodeState<SettingsEntry>, SettingsEntry.ItemGenerationArguments)) in
let proxySettings: ProxySettings let proxySettings: ProxySettings
if let value = preferences.values[PreferencesKeys.proxySettings] as? ProxySettings { if let value = preferences.values[PreferencesKeys.proxySettings] as? ProxySettings {
proxySettings = value proxySettings = value
@ -679,7 +705,9 @@ public func settingsController(account: Account, accountManager: AccountManager)
} }
} }
let listState = ItemListNodeState(entries: settingsEntries(presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, hasPassport: hasPassport), style: .blocks) let (hasPassport, hasWatchApp) = hasPassportAndWatch
let listState = ItemListNodeState(entries: settingsEntries(presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, hasPassport: hasPassport, hasWatchApp: hasWatchApp), style: .blocks)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {

View File

@ -178,7 +178,7 @@ public func chatMessageSticker(account: Account, file: TelegramMediaFile, small:
return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in
return { arguments in return { arguments in
let context = DrawingContext(size: arguments.drawingSize, clear: true) let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil)
let drawingRect = arguments.drawingRect let drawingRect = arguments.drawingRect
let fittedSize = arguments.imageSize let fittedSize = arguments.imageSize
@ -224,7 +224,12 @@ public func chatMessageSticker(account: Account, file: TelegramMediaFile, small:
} }
context.withFlippedContext { c in context.withFlippedContext { c in
if let color = arguments.emptyColor {
c.setBlendMode(.normal)
c.fill(drawingRect)
}
c.setBlendMode(.copy) c.setBlendMode(.copy)
if let blurredThumbnailImage = blurredThumbnailImage { if let blurredThumbnailImage = blurredThumbnailImage {
c.interpolationQuality = .low c.interpolationQuality = .low
let thumbnailScaledInset = thumbnailInset * (fittedRect.width / blurredThumbnailImage.size.width) let thumbnailScaledInset = thumbnailInset * (fittedRect.width / blurredThumbnailImage.size.width)

View File

@ -523,7 +523,7 @@ func storageUsageController(account: Account) -> ViewController {
if !items.isEmpty { if !items.isEmpty {
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize))").0, action: { items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize))").0, action: {
if let statsPromise = statsPromise { if let statsPromise = statsPromise {
var clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 }) let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
//var clearSize: Int64 = 0 //var clearSize: Int64 = 0
var clearMediaIds = Set<MediaId>() var clearMediaIds = Set<MediaId>()

View File

@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>
@interface TGBridgeAudioDecoder : NSObject
- (instancetype)initWithURL:(NSURL *)url outputUrl:(NSURL *)outputURL;
- (void)startWithCompletion:(void (^)(void))completion;
@end

View File

@ -0,0 +1,200 @@
#import "TGBridgeAudioDecoder.h"
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import <SSignalKit/SSignalKit.h>
#import "opusfile.h"
#import "opusenc.h"
const NSInteger TGBridgeAudioDecoderInputSampleRate = 48000;
const NSInteger TGBridgeAudioDecoderResultSampleRate = 24000;
const NSUInteger TGBridgeAudioDecoderBufferSize = 32768;
#define checkResult(result,operation) (_checkResultLite((result),(operation),__FILE__,__LINE__))
struct TGAudioBuffer
{
NSUInteger capacity;
uint8_t *data;
NSUInteger size;
int64_t pcmOffset;
};
inline TGAudioBuffer *TGAudioBufferWithCapacity(NSUInteger capacity)
{
TGAudioBuffer *audioBuffer = (TGAudioBuffer *)malloc(sizeof(TGAudioBuffer));
audioBuffer->capacity = capacity;
audioBuffer->data = (uint8_t *)malloc(capacity);
audioBuffer->size = 0;
audioBuffer->pcmOffset = 0;
return audioBuffer;
}
inline void TGAudioBufferDispose(TGAudioBuffer *audioBuffer)
{
if (audioBuffer != NULL)
{
free(audioBuffer->data);
free(audioBuffer);
}
}
static inline bool _checkResultLite(OSStatus result, const char *operation, const char* file, int line)
{
if ( result != noErr )
{
NSLog(@"%s:%d: %s result %d %08X %4.4s\n", file, line, operation, (int)result, (int)result, (char*)&result);
return NO;
}
return YES;
}
@interface TGBridgeAudioDecoder ()
{
NSURL *_url;
NSURL *_resultURL;
OggOpusFile *_opusFile;
bool _finished;
bool _cancelled;
}
@end
@implementation TGBridgeAudioDecoder
- (instancetype)initWithURL:(NSURL *)url outputUrl:(NSURL *)outputUrl
{
self = [super init];
if (self != nil)
{
_url = url;
int64_t randomId = 0;
arc4random_buf(&randomId, 8);
_resultURL = outputUrl;
}
return self;
}
- (void)startWithCompletion:(void (^)(void))completion
{
[[TGBridgeAudioDecoder processingQueue] dispatch:^
{
int error = OPUS_OK;
_opusFile = op_open_file(_url.path.UTF8String, &error);
if (_opusFile == NULL || error != OPUS_OK)
{
return;
}
AudioStreamBasicDescription sourceFormat;
sourceFormat.mSampleRate = TGBridgeAudioDecoderInputSampleRate;
sourceFormat.mFormatID = kAudioFormatLinearPCM;
sourceFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
sourceFormat.mFramesPerPacket = 1;
sourceFormat.mChannelsPerFrame = 1;
sourceFormat.mBitsPerChannel = 16;
sourceFormat.mBytesPerPacket = 2;
sourceFormat.mBytesPerFrame = 2;
AudioStreamBasicDescription destFormat;
memset(&destFormat, 0, sizeof(destFormat));
destFormat.mChannelsPerFrame = sourceFormat.mChannelsPerFrame;
destFormat.mFormatID = kAudioFormatMPEG4AAC;
destFormat.mSampleRate = TGBridgeAudioDecoderResultSampleRate;
UInt32 size = sizeof(destFormat);
if (!checkResult(AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &size, &destFormat),
"AudioFormatGetProperty(kAudioFormatProperty_FormatInfo)"))
{
return;
}
ExtAudioFileRef destinationFile;
if (!checkResult(ExtAudioFileCreateWithURL((__bridge CFURLRef)_resultURL, kAudioFileM4AType, &destFormat, NULL, kAudioFileFlags_EraseFile, &destinationFile), "ExtAudioFileCreateWithURL"))
{
return;
}
if (!checkResult(ExtAudioFileSetProperty(destinationFile, kExtAudioFileProperty_ClientDataFormat, size, &sourceFormat),
"ExtAudioFileSetProperty(destinationFile, kExtAudioFileProperty_ClientDataFormat"))
{
return;
}
bool canResumeAfterInterruption = false;
AudioConverterRef converter;
size = sizeof(converter);
if (checkResult(ExtAudioFileGetProperty(destinationFile, kExtAudioFileProperty_AudioConverter, &size, &converter),
"ExtAudioFileGetProperty(kExtAudioFileProperty_AudioConverter;)"))
{
UInt32 canResume = 0;
size = sizeof(canResume);
if (AudioConverterGetProperty(converter, kAudioConverterPropertyCanResumeFromInterruption, &size, &canResume) == noErr)
canResumeAfterInterruption = canResume;
}
uint8_t srcBuffer[TGBridgeAudioDecoderBufferSize];
while (!_cancelled)
{
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mNumberChannels = sourceFormat.mChannelsPerFrame;
bufferList.mBuffers[0].mDataByteSize = TGBridgeAudioDecoderBufferSize;
bufferList.mBuffers[0].mData = srcBuffer;
uint32_t writtenOutputBytes = 0;
while (writtenOutputBytes < TGBridgeAudioDecoderBufferSize)
{
int32_t readSamples = op_read(_opusFile, (opus_int16 *)(srcBuffer + writtenOutputBytes), (TGBridgeAudioDecoderBufferSize - writtenOutputBytes) / sourceFormat.mBytesPerFrame, NULL);
if (readSamples > 0)
writtenOutputBytes += readSamples * sourceFormat.mBytesPerFrame;
else
break;
}
bufferList.mBuffers[0].mDataByteSize = writtenOutputBytes;
int32_t nFrames = writtenOutputBytes / sourceFormat.mBytesPerFrame;
if (nFrames == 0)
break;
OSStatus status = ExtAudioFileWrite(destinationFile, nFrames, &bufferList);
if (status == kExtAudioFileError_CodecUnavailableInputConsumed)
{
//TGLog(@"1");
}
else if (status == kExtAudioFileError_CodecUnavailableInputNotConsumed)
{
//TGLog(@"2");
}
else if (!checkResult(status, "ExtAudioFileWrite"))
{
//TGLog(@"3");
}
}
ExtAudioFileDispose(destinationFile);
if (completion != nil)
completion();
}];
}
+ (SQueue *)processingQueue
{
static SQueue *queue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
static const char *queueSpecific = "org.telegram.opusAudioDecoderQueue";
dispatch_queue_t dispatchQueue = dispatch_queue_create("org.telegram.opusAudioDecoderQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(dispatchQueue, queueSpecific, (void *)queueSpecific, NULL);
queue = [SQueue wrapConcurrentNativeQueue:dispatchQueue];
});
return queue;
}
@end

View File

@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>
@class TGDataItem;
@class TGLiveUploadActorData;
@interface TGBridgeAudioEncoder : NSObject
- (instancetype)initWithURL:(NSURL *)url;
- (void)startWithCompletion:(void (^)(TGDataItem *, int32_t))completion;
@end

View File

@ -0,0 +1,211 @@
#import "TGBridgeAudioEncoder.h"
#import <AVFoundation/AVFoundation.h>
#import <SSignalKit/SSignalKit.h>
#import "opus.h"
#import "opusenc.h"
#import "TGDataItem.h"
const NSInteger TGBridgeAudioEncoderSampleRate = 16000;
@interface TGBridgeAudioEncoder ()
{
AVAssetReader *_assetReader;
AVAssetReaderOutput *_readerOutput;
NSMutableData *_audioBuffer;
TGDataItem *_tempFileItem;
TGOggOpusWriter *_oggWriter;
}
@end
@implementation TGBridgeAudioEncoder
- (instancetype)initWithURL:(NSURL *)url
{
self = [super init];
if (self != nil)
{
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
if (asset == nil || asset.tracks.count == 0)
{
//TGLog(@"Asset create fail");
return nil;
}
NSError *error;
_assetReader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
NSDictionary *outputSettings = @
{
AVFormatIDKey: @(kAudioFormatLinearPCM),
AVSampleRateKey: @(TGBridgeAudioEncoderSampleRate),
AVNumberOfChannelsKey: @1,
AVLinearPCMBitDepthKey: @16,
AVLinearPCMIsFloatKey: @false,
AVLinearPCMIsBigEndianKey: @false,
AVLinearPCMIsNonInterleaved: @false
};
_readerOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:asset.tracks audioSettings:outputSettings];
[_assetReader addOutput:_readerOutput];
_tempFileItem = [[TGDataItem alloc] init];
}
return self;
}
- (void)dealloc
{
[self cleanup];
}
- (void)cleanup
{
_oggWriter = nil;
}
+ (SQueue *)processingQueue
{
static SQueue *queue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
static const char *queueSpecific = "org.telegram.opusAudioEncoderQueue";
dispatch_queue_t dispatchQueue = dispatch_queue_create("org.telegram.opusAudioEncoderQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(dispatchQueue, queueSpecific, (void *)queueSpecific, NULL);
queue = [SQueue wrapConcurrentNativeQueue:dispatchQueue];
});
return queue;
}
- (void)startWithCompletion:(void (^)(TGDataItem *, int32_t))completion
{
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
[[TGBridgeAudioEncoder processingQueue] dispatch:^
{
_oggWriter = [[TGOggOpusWriter alloc] init];
if (![_oggWriter beginWithDataItem:_tempFileItem])
{
//TGLog(@"[TGBridgeAudioEncoder#%x error initializing ogg opus writer]", self);
[self cleanup];
return;
}
[_assetReader startReading];
while (_assetReader.status != AVAssetReaderStatusCompleted)
{
if (_assetReader.status == AVAssetReaderStatusReading)
{
CMSampleBufferRef nextBuffer = [_readerOutput copyNextSampleBuffer];
if (nextBuffer)
{
AudioBufferList abl;
CMBlockBufferRef blockBuffer;
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);
[[TGBridgeAudioEncoder processingQueue] dispatch:^
{
[self _processBuffer:&abl.mBuffers[0]];
CFRelease(nextBuffer);
CFRelease(blockBuffer);
}];
}
else
{
break;
}
}
}
TGDataItem *dataItemResult = nil;
NSTimeInterval durationResult = 0.0;
NSUInteger totalBytes = 0;
if (_assetReader.status == AVAssetReaderStatusCompleted)
{
if (_oggWriter != nil && [_oggWriter writeFrame:NULL frameByteCount:0])
{
dataItemResult = _tempFileItem;
durationResult = [_oggWriter encodedDuration];
totalBytes = [_oggWriter encodedBytes];
}
[self cleanup];
}
//TGLog(@"[TGBridgeAudioEncoder#%x convert time: %f ms]", self, (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0);
if (completion != nil)
completion(dataItemResult, (int32_t)durationResult);
}];
}
- (void)_processBuffer:(AudioBuffer const *)buffer
{
@autoreleasepool
{
if (_oggWriter == nil)
return;
static const int millisecondsPerPacket = 60;
static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 1000 * millisecondsPerPacket * 2;
unsigned char currentEncoderPacket[encoderPacketSizeInBytes];
int bufferOffset = 0;
while (true)
{
int currentEncoderPacketSize = 0;
while (currentEncoderPacketSize < encoderPacketSizeInBytes)
{
if (_audioBuffer.length != 0)
{
int takenBytes = MIN((int)_audioBuffer.length, encoderPacketSizeInBytes - currentEncoderPacketSize);
if (takenBytes != 0)
{
memcpy(currentEncoderPacket + currentEncoderPacketSize, _audioBuffer.bytes, takenBytes);
[_audioBuffer replaceBytesInRange:NSMakeRange(0, takenBytes) withBytes:NULL length:0];
currentEncoderPacketSize += takenBytes;
}
}
else if (bufferOffset < (int)buffer->mDataByteSize)
{
int takenBytes = MIN((int)buffer->mDataByteSize - bufferOffset, encoderPacketSizeInBytes - currentEncoderPacketSize);
if (takenBytes != 0)
{
memcpy(currentEncoderPacket + currentEncoderPacketSize, ((const char *)buffer->mData) + bufferOffset, takenBytes);
bufferOffset += takenBytes;
currentEncoderPacketSize += takenBytes;
}
}
else
break;
}
if (currentEncoderPacketSize < encoderPacketSizeInBytes)
{
if (_audioBuffer == nil)
_audioBuffer = [[NSMutableData alloc] initWithCapacity:encoderPacketSizeInBytes];
[_audioBuffer appendBytes:currentEncoderPacket length:currentEncoderPacketSize];
break;
}
else
{
[_oggWriter writeFrame:currentEncoderPacket frameByteCount:(NSUInteger)currentEncoderPacketSize];
}
}
}
}
@end

View File

@ -99,6 +99,8 @@ public final class TelegramApplicationContext {
} }
private var hasOngoingCallDisposable: Disposable? private var hasOngoingCallDisposable: Disposable?
public var watchManager: WatchManager?
private var immediateExperimentalUISettingsValue = Atomic<ExperimentalUISettings>(value: ExperimentalUISettings.defaultSettings) private var immediateExperimentalUISettingsValue = Atomic<ExperimentalUISettings>(value: ExperimentalUISettings.defaultSettings)
public var immediateExperimentalUISettings: ExperimentalUISettings { public var immediateExperimentalUISettings: ExperimentalUISettings {
return self.immediateExperimentalUISettingsValue.with { $0 } return self.immediateExperimentalUISettingsValue.with { $0 }

View File

@ -28,4 +28,6 @@ module TelegramUIPrivateModule {
header "../TGEmojiSuggestions.h" header "../TGEmojiSuggestions.h"
header "../TGChannelIntroController.h" header "../TGChannelIntroController.h"
header "../EDSunriseSet.h" header "../EDSunriseSet.h"
header "../TGBridgeAudioDecoder.h"
header "../TGBridgeAudioEncoder.h"
} }

View File

@ -13,15 +13,17 @@ public struct TransformImageArguments: Equatable {
public let boundingSize: CGSize public let boundingSize: CGSize
public let intrinsicInsets: UIEdgeInsets public let intrinsicInsets: UIEdgeInsets
public let resizeMode: TransformImageResizeMode public let resizeMode: TransformImageResizeMode
public let emptyColor: UIColor public let emptyColor: UIColor?
public let scale: CGFloat?
public init(corners: ImageCorners, imageSize: CGSize, boundingSize: CGSize, intrinsicInsets: UIEdgeInsets, resizeMode: TransformImageResizeMode = .fill(.black), emptyColor: UIColor = .white) { public init(corners: ImageCorners, imageSize: CGSize, boundingSize: CGSize, intrinsicInsets: UIEdgeInsets, resizeMode: TransformImageResizeMode = .fill(.black), emptyColor: UIColor? = nil, scale: CGFloat? = nil) {
self.corners = corners self.corners = corners
self.imageSize = imageSize self.imageSize = imageSize
self.boundingSize = boundingSize self.boundingSize = boundingSize
self.intrinsicInsets = intrinsicInsets self.intrinsicInsets = intrinsicInsets
self.resizeMode = resizeMode self.resizeMode = resizeMode
self.emptyColor = emptyColor self.emptyColor = emptyColor
self.scale = scale
} }
public var drawingSize: CGSize { public var drawingSize: CGSize {

View File

@ -0,0 +1,36 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramCore
public final class WatchManagerArguments {
public let appInstalled: Signal<Bool, NoError>
public let navigateToMessageRequested: Signal<MessageId, NoError>
public let runningRequests: Signal<Bool, NoError>
public init(appInstalled: Signal<Bool, NoError>, navigateToMessageRequested: Signal<MessageId, NoError>, runningRequests: Signal<Bool, NoError>) {
self.appInstalled = appInstalled
self.navigateToMessageRequested = navigateToMessageRequested
self.runningRequests = runningRequests
}
}
public final class WatchManager {
private let arguments: WatchManagerArguments?
public init(arguments: WatchManagerArguments?) {
self.arguments = arguments
}
public var watchAppInstalled: Signal<Bool, NoError> {
return self.arguments?.appInstalled ?? .single(false)
}
public var navigateToMessageRequested: Signal<MessageId, NoError> {
return self.arguments?.navigateToMessageRequested ?? .never()
}
public var runningRequests: Signal<Bool, NoError> {
return self.arguments?.runningRequests ?? .single(false)
}
}

View File

@ -0,0 +1,68 @@
import Foundation
import Postbox
import SwiftSignalKit
public struct WatchPresetSettings: PreferencesEntry, Equatable {
public var customPresets: [String : String]
public static var defaultSettings: WatchPresetSettings {
return WatchPresetSettings(presets: [:])
}
public init(presets: [String : String]) {
self.customPresets = presets
}
public init(decoder: PostboxDecoder) {
let keys = decoder.decodeStringArrayForKey("presetKeys")
let values = decoder.decodeStringArrayForKey("presetValues")
if keys.count == values.count {
var presets: [String : String] = [:]
for i in 0 ..< keys.count {
presets[keys[i]] = values[i]
}
self.customPresets = presets
} else {
self.customPresets = [:]
}
}
public func encode(_ encoder: PostboxEncoder) {
let keys = self.customPresets.keys.sorted()
let values = keys.reduce([String]()) { (values, index) -> [String] in
var values = values
if let value = self.customPresets[index] {
values.append(value)
}
return values
}
encoder.encodeStringArray(keys, forKey: "presetKeys")
encoder.encodeStringArray(values, forKey: "presetValues")
}
public func isEqual(to: PreferencesEntry) -> Bool {
if let to = to as? WatchPresetSettings {
return self == to
} else {
return false
}
}
public static func ==(lhs: WatchPresetSettings, rhs: WatchPresetSettings) -> Bool {
return lhs.customPresets == rhs.customPresets
}
}
func updateWatchPresetSettingsInteractively(postbox: Postbox, _ f: @escaping (WatchPresetSettings) -> WatchPresetSettings) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.watchPresetSettings, { entry in
let currentSettings: WatchPresetSettings
if let entry = entry as? WatchPresetSettings {
currentSettings = entry
} else {
currentSettings = WatchPresetSettings.defaultSettings
}
return f(currentSettings)
})
}
}

View File

@ -0,0 +1,148 @@
import Foundation
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
private final class WatchSettingsControllerArguments {
let updatePreset: (String, String) -> Void
init(updatePreset: @escaping (String, String) -> Void) {
self.updatePreset = updatePreset
}
}
private enum WatchSettingsSection: Int32 {
case replyPresets
}
private enum WatchSettingsControllerEntry: ItemListNodeEntry {
case replyPresetsHeader(PresentationTheme, String)
case replyPreset(PresentationTheme, String, String, String, Int32)
case replyPresetsInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
case .replyPresetsHeader, .replyPreset, .replyPresetsInfo:
return WatchSettingsSection.replyPresets.rawValue
}
}
var stableId: Int32 {
switch self {
case .replyPresetsHeader:
return 0
case let .replyPreset(_, _, _, _, index):
return 1 + index
case .replyPresetsInfo:
return 100
}
}
static func ==(lhs: WatchSettingsControllerEntry, rhs: WatchSettingsControllerEntry) -> Bool {
switch lhs {
case let .replyPresetsHeader(lhsTheme, lhsText):
if case let .replyPresetsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .replyPreset(lhsTheme, lhsIdentifier, lhsPlaceholder, lhsValue, lhsIndex):
if case let .replyPreset(rhsTheme, rhsIdentifier, rhsPlaceholder, rhsValue, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsIdentifier == rhsIdentifier, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue, lhsIndex == rhsIndex {
return true
} else {
return false
}
case let .replyPresetsInfo(lhsTheme, lhsText):
if case let .replyPresetsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
}
}
static func <(lhs: WatchSettingsControllerEntry, rhs: WatchSettingsControllerEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(_ arguments: WatchSettingsControllerArguments) -> ListViewItem {
switch self {
case let .replyPresetsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .replyPreset(theme, identifier, placeholder, value, _):
return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: ""), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: true), spacing: 0.0, sectionId: self.section, textUpdated: { updatedText in
arguments.updatePreset(identifier, updatedText.trimmingCharacters(in: .whitespacesAndNewlines))
}, action: {})
case let .replyPresetsInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
}
}
}
private func watchSettingsControllerEntries(presentationData: PresentationData, customPresets: [String : String]) -> [WatchSettingsControllerEntry] {
var entries: [WatchSettingsControllerEntry] = []
let defaultSuggestions : [(Int32, String, String)] = [
(0, "OK", presentationData.strings.Watch_Suggestion_OK),
(1, "Thanks", presentationData.strings.Watch_Suggestion_Thanks),
(2, "WhatsUp", presentationData.strings.Watch_Suggestion_WhatsUp),
(3, "TalkLater", presentationData.strings.Watch_Suggestion_TalkLater),
(4, "CantTalk", presentationData.strings.Watch_Suggestion_CantTalk),
(5, "HoldOn", presentationData.strings.Watch_Suggestion_HoldOn),
(6, "BRB", presentationData.strings.Watch_Suggestion_BRB),
(7, "OnMyWay", presentationData.strings.Watch_Suggestion_OnMyWay)
]
entries.append(.replyPresetsHeader(presentationData.theme, presentationData.strings.AppleWatch_ReplyPresets))
for (index, identifier, placeholder) in defaultSuggestions {
entries.append(.replyPreset(presentationData.theme, identifier, placeholder, customPresets[identifier] ?? "", index))
}
entries.append(.replyPresetsInfo(presentationData.theme, presentationData.strings.AppleWatch_ReplyPresetsHelp))
return entries
}
public func watchSettingsController(account: Account) -> ViewController {
var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController) -> Void)?
let updateDisposable = MetaDisposable()
let arguments = WatchSettingsControllerArguments(updatePreset: { identifier, text in
updateDisposable.set((.complete() |> delay(1.0, queue: Queue.mainQueue()) |> then(updateWatchPresetSettingsInteractively(postbox: account.postbox, { current in
var updatedPresets = current.customPresets
if !text.isEmpty {
updatedPresets[identifier] = text
} else {
updatedPresets.removeValue(forKey: identifier)
}
return WatchPresetSettings(presets: updatedPresets)
}))).start())
})
let watchPresetSettingsKey = ApplicationSpecificPreferencesKeys.watchPresetSettings
let preferences = account.postbox.preferencesView(keys: [watchPresetSettingsKey])
let signal = combineLatest(account.telegramApplicationContext.presentationData, preferences)
|> deliverOnMainQueue
|> map { presentationData, preferences -> (ItemListControllerState, (ItemListNodeState<WatchSettingsControllerEntry>, WatchSettingsControllerEntry.ItemGenerationArguments)) in
let settings = (preferences.values[watchPresetSettingsKey] as? WatchPresetSettings) ?? WatchPresetSettings.defaultSettings
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.AppleWatch_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(entries: watchSettingsControllerEntries(presentationData: presentationData, customPresets: settings.customPresets), style: .blocks, animateChanges: false)
return (controllerState, (listState, arguments))
}
let controller = ItemListController(account: account, state: signal)
pushControllerImpl = { [weak controller] c in
(controller?.navigationController as? NavigationController)?.pushViewController(c)
}
presentControllerImpl = { [weak controller] c in
controller?.present(c, in: .window(.root))
}
return controller
}