Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
overtake
2019-09-01 10:09:56 +03:00
12 changed files with 124 additions and 74 deletions

View File

@@ -4676,13 +4676,13 @@ Any member of this group will be able to see messages in the channel.";
"EditTheme.EditTitle" = "Edit Theme";
"EditTheme.Title" = "Theme Name";
"EditTheme.ShortLink" = "link";
"EditTheme.ShortLinkInfo" = "Short Link Info";
"EditTheme.ShortLinkInfo" = "Your theme will be updated for all users each time you change it. Anyone can install it using this link.\n\nTheme links must be longer than 5 characters and can use a-z, 0-9 and underscores.";
"EditTheme.Preview" = "CHAT PREVIEW";
"EditTheme.UploadNewTheme" = "Select a File...";
"EditTheme.UploadEditedTheme" = "Select Updated File...";
"EditTheme.UploadNewInfo" = "This theme will be based on your current theme and wallpaper. Otherwise, you can use a custom theme file if you already have one.";
"EditTheme.UploadNewTheme" = "Create from File...";
"EditTheme.UploadEditedTheme" = "Update from File...";
"EditTheme.UploadNewInfo" = "This theme will be based on your current theme and wallpaper. Alternatively, you can use a custom theme file if you already have one.";
"EditTheme.UploadEditedInfo" = "You can select a new file to update the theme. It will be updated for all users.";
"EditTheme.ThemeTemplateAlert" = "A copy of your theme template has been added to your Saved Messages.";
"EditTheme.ThemeTemplateAlert" = "New Theme Added\n\nPress and hold on your theme to edit it or get a sharing link. Users who install your theme will get automatic updates each time you change it.\n\nFor advanced editing purposes, you can find a file with your theme in Saved Messages.";
"EditTheme.FileReadError" = "Invalid theme file";
"Wallpaper.ErrorNotFound" = "Sorry, this chat background doesn't seem to exist.";

View File

@@ -108,6 +108,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
case phoneDiscoveryHeader(PresentationTheme, String)
case phoneDiscoveryEverybody(PresentationTheme, String, Bool)
case phoneDiscoveryMyContacts(PresentationTheme, String, Bool)
case phoneDiscoveryInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
@@ -123,7 +124,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return SelectivePrivacySettingsSection.callsP2PPeers.rawValue
case .callsIntegrationEnabled, .callsIntegrationInfo:
return SelectivePrivacySettingsSection.callsIntegrationEnabled.rawValue
case .phoneDiscoveryHeader, .phoneDiscoveryEverybody, .phoneDiscoveryMyContacts:
case .phoneDiscoveryHeader, .phoneDiscoveryEverybody, .phoneDiscoveryMyContacts, .phoneDiscoveryInfo:
return SelectivePrivacySettingsSection.phoneDiscovery.rawValue
}
}
@@ -150,34 +151,36 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return 8
case .phoneDiscoveryMyContacts:
return 9
case .exceptionsHeader:
case .phoneDiscoveryInfo:
return 10
case .disableFor:
case .exceptionsHeader:
return 11
case .enableFor:
case .disableFor:
return 12
case .peersInfo:
case .enableFor:
return 13
case .callsP2PHeader:
case .peersInfo:
return 14
case .callsP2PAlways:
case .callsP2PHeader:
return 15
case .callsP2PContacts:
case .callsP2PAlways:
return 16
case .callsP2PNever:
case .callsP2PContacts:
return 17
case .callsP2PInfo:
case .callsP2PNever:
return 18
case .callsP2PDisableFor:
case .callsP2PInfo:
return 19
case .callsP2PEnableFor:
case .callsP2PDisableFor:
return 20
case .callsP2PPeersInfo:
case .callsP2PEnableFor:
return 21
case .callsIntegrationEnabled:
case .callsP2PPeersInfo:
return 22
case .callsIntegrationInfo:
case .callsIntegrationEnabled:
return 23
case .callsIntegrationInfo:
return 24
}
}
@@ -327,6 +330,12 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
} else {
return false
}
case let .phoneDiscoveryInfo(lhsTheme, lhsText):
if case let .phoneDiscoveryInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
}
}
@@ -383,7 +392,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
arguments.updateCallP2PMode?(.nobody)
})
case let .callsP2PInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .callsP2PDisableFor(theme, title, value):
return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openSelective(.callP2P, false)
@@ -410,6 +419,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updatePhoneDiscovery?(false)
})
case let .phoneDiscoveryInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
}
}
}
@@ -522,7 +533,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
var entries: [SelectivePrivacySettingsEntry] = []
let settingTitle: String
let settingInfoText: String
let settingInfoText: String?
let disableForText: String
let enableForText: String
switch kind {
@@ -553,8 +564,8 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
enableForText = presentationData.strings.Privacy_GroupsAndChannels_AlwaysAllow
case .phoneNumber:
settingTitle = presentationData.strings.PrivacyPhoneNumberSettings_WhoCanSeeMyPhoneNumber
if state.setting == .nobody, state.phoneDiscoveryEnabled == false {
settingInfoText = presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp
if state.setting == .nobody {
settingInfoText = nil
} else {
settingInfoText = presentationData.strings.PrivacyPhoneNumberSettings_CustomHelp
}
@@ -590,12 +601,15 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
case .groupInvitations, .profilePhoto:
break
}
entries.append(.settingInfo(presentationData.theme, settingInfoText))
if let settingInfoText = settingInfoText {
entries.append(.settingInfo(presentationData.theme, settingInfoText))
}
if case .phoneNumber = kind, state.setting == .nobody {
entries.append(.phoneDiscoveryHeader(presentationData.theme, presentationData.strings.PrivacyPhoneNumberSettings_DiscoveryHeader))
entries.append(.phoneDiscoveryEverybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.phoneDiscoveryEnabled != false))
entries.append(.phoneDiscoveryMyContacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.phoneDiscoveryEnabled == false))
entries.append(.phoneDiscoveryInfo(presentationData.theme, state.phoneDiscoveryEnabled != false ? presentationData.strings.PrivacyPhoneNumberSettings_CustomHelp : presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp))
}
entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions))

View File

@@ -131,7 +131,7 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
func item(_ arguments: EditThemeControllerArguments) -> ListViewItem {
switch self {
case let .title(theme, strings, title, text, done):
case let .title(theme, strings, title, text, _):
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: title, type: .regular(capitalization: true, autocorrection: false), returnKeyType: .default, clearType: .onFocus, tag: EditThemeEntryTag.title, sectionId: self.section, textUpdated: { value in
arguments.updateState { current in
var state = current
@@ -152,7 +152,7 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
})
case let .slugInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section)
case let .chatPreviewHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .chatPreview(theme, componentTheme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder):
@@ -162,7 +162,7 @@ private enum EditThemeControllerEntry: ItemListNodeEntry {
arguments.openFile()
})
case let .uploadInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section)
}
}
}

View File

@@ -12,22 +12,24 @@ final class ThemeGridControllerItem: GridItem {
let context: AccountContext
let wallpaper: TelegramWallpaper
let index: Int
let editable: Bool
let selected: Bool
let interaction: ThemeGridControllerInteraction
let section: GridSection? = nil
init(context: AccountContext, wallpaper: TelegramWallpaper, index: Int, selected: Bool, interaction: ThemeGridControllerInteraction) {
init(context: AccountContext, wallpaper: TelegramWallpaper, index: Int, editable: Bool, selected: Bool, interaction: ThemeGridControllerInteraction) {
self.context = context
self.wallpaper = wallpaper
self.index = index
self.editable = editable
self.selected = selected
self.interaction = interaction
}
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
let node = ThemeGridControllerItemNode()
node.setup(context: self.context, wallpaper: self.wallpaper, selected: self.selected, interaction: self.interaction, synchronousLoad: synchronousLoad)
node.setup(context: self.context, wallpaper: self.wallpaper, editable: self.editable, selected: self.selected, interaction: self.interaction, synchronousLoad: synchronousLoad)
return node
}
@@ -36,7 +38,7 @@ final class ThemeGridControllerItem: GridItem {
assertionFailure()
return
}
node.setup(context: self.context, wallpaper: self.wallpaper, selected: self.selected, interaction: self.interaction, synchronousLoad: false)
node.setup(context: self.context, wallpaper: self.wallpaper, editable: self.editable, selected: self.selected, interaction: self.interaction, synchronousLoad: false)
}
}
@@ -44,7 +46,7 @@ final class ThemeGridControllerItemNode: GridItemNode {
private let wallpaperNode: SettingsThemeWallpaperNode
private var selectionNode: GridMessageSelectionNode?
private var currentState: (AccountContext, TelegramWallpaper, Bool, Bool)?
private var currentState: (AccountContext, TelegramWallpaper, Bool, Bool, Bool)?
private var interaction: ThemeGridControllerInteraction?
override init() {
@@ -61,11 +63,11 @@ final class ThemeGridControllerItemNode: GridItemNode {
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
func setup(context: AccountContext, wallpaper: TelegramWallpaper, selected: Bool, interaction: ThemeGridControllerInteraction, synchronousLoad: Bool) {
func setup(context: AccountContext, wallpaper: TelegramWallpaper, editable: Bool, selected: Bool, interaction: ThemeGridControllerInteraction, synchronousLoad: Bool) {
self.interaction = interaction
if self.currentState == nil || self.currentState!.0 !== context || wallpaper != self.currentState!.1 || selected != self.currentState!.2 || synchronousLoad != self.currentState!.3 {
self.currentState = (context, wallpaper, selected, synchronousLoad)
if self.currentState == nil || self.currentState!.0 !== context || wallpaper != self.currentState!.1 || selected != self.currentState!.2 || synchronousLoad != self.currentState!.3 || editable != self.currentState!.4 {
self.currentState = (context, wallpaper, selected, synchronousLoad, editable)
self.updateSelectionState(animated: false)
self.setNeedsLayout()
}
@@ -73,14 +75,14 @@ final class ThemeGridControllerItemNode: GridItemNode {
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
if let (_, wallpaper, _, _) = self.currentState {
if let (_, wallpaper, _, _, _) = self.currentState {
self.interaction?.openWallpaper(wallpaper)
}
}
}
func updateSelectionState(animated: Bool) {
if let (context, wallpaper, _, _) = self.currentState {
if let (context, wallpaper, _, _, editable) = self.currentState {
var editing = false
var id: Int64?
if case let .file(file) = wallpaper {
@@ -94,7 +96,7 @@ final class ThemeGridControllerItemNode: GridItemNode {
editing = active
selectedIndices = indices
}
if let id = id, editing {
if let id = id, editing && editable {
let selected = selectedIndices.contains(id)
if let selectionNode = self.selectionNode {
@@ -136,7 +138,7 @@ final class ThemeGridControllerItemNode: GridItemNode {
super.layout()
let bounds = self.bounds
if let (context, wallpaper, selected, synchronousLoad) = self.currentState {
if let (context, wallpaper, selected, synchronousLoad, _) = self.currentState {
self.wallpaperNode.setWallpaper(context: context, wallpaper: wallpaper, selected: selected, size: bounds.size, synchronousLoad: synchronousLoad)
self.selectionNode?.frame = CGRect(origin: CGPoint(), size: bounds.size)
}

View File

@@ -84,10 +84,11 @@ final class ThemeGridControllerInteraction {
private struct ThemeGridControllerEntry: Comparable, Identifiable {
let index: Int
let wallpaper: TelegramWallpaper
let selected: Bool
let isEditable: Bool
let isSelected: Bool
static func ==(lhs: ThemeGridControllerEntry, rhs: ThemeGridControllerEntry) -> Bool {
return lhs.index == rhs.index && lhs.wallpaper == rhs.wallpaper && lhs.selected == rhs.selected
return lhs.index == rhs.index && lhs.wallpaper == rhs.wallpaper && lhs.isEditable == rhs.isEditable && lhs.isSelected == rhs.isSelected
}
static func <(lhs: ThemeGridControllerEntry, rhs: ThemeGridControllerEntry) -> Bool {
@@ -115,7 +116,7 @@ private struct ThemeGridControllerEntry: Comparable, Identifiable {
}
func item(context: AccountContext, interaction: ThemeGridControllerInteraction) -> ThemeGridControllerItem {
return ThemeGridControllerItem(context: context, wallpaper: self.wallpaper, index: self.index, selected: self.selected, interaction: interaction)
return ThemeGridControllerItem(context: context, wallpaper: self.wallpaper, index: self.index, editable: self.isEditable, selected: self.isSelected, interaction: interaction)
}
}
@@ -364,14 +365,20 @@ final class ThemeGridControllerNode: ASDisplayNode {
var entries: [ThemeGridControllerEntry] = []
var index = 1
entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, selected: true), at: 0)
var isSelectedEditable = true
if case .builtin = presentationData.chatWallpaper {
isSelectedEditable = false
} else if areWallpapersEqual(presentationData.chatWallpaper, presentationData.theme.chat.defaultWallpaper) {
isSelectedEditable = false
}
entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, isEditable: isSelectedEditable, isSelected: true), at: 0)
var defaultWallpaper: TelegramWallpaper?
if !areWallpapersEqual(presentationData.chatWallpaper, presentationData.theme.chat.defaultWallpaper) {
if case .builtin = presentationData.theme.chat.defaultWallpaper {
} else {
defaultWallpaper = presentationData.theme.chat.defaultWallpaper
entries.insert(ThemeGridControllerEntry(index: 1, wallpaper: presentationData.theme.chat.defaultWallpaper, selected: false), at: 1)
entries.insert(ThemeGridControllerEntry(index: 1, wallpaper: presentationData.theme.chat.defaultWallpaper, isEditable: false, isSelected: false), at: 1)
index += 1
}
}
@@ -400,8 +407,12 @@ final class ThemeGridControllerNode: ASDisplayNode {
if let defaultWallpaper = defaultWallpaper, areWallpapersEqual(defaultWallpaper, wallpaper) {
isDefault = true
}
var isEditable = true
if case .builtin = wallpaper {
isEditable = false
}
if !selected && !isDefault {
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: false))
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, isEditable: isEditable, isSelected: false))
}
index += 1
}

View File

@@ -145,6 +145,7 @@ private final class ThemeSettingsThemeItemIconNode : ASDisplayNode {
private var theme: PresentationThemeReference?
private var currentTheme: PresentationTheme?
private var accentColor: UIColor?
private var bordered: Bool?
private var selected: Bool?
@@ -175,9 +176,10 @@ private final class ThemeSettingsThemeItemIconNode : ASDisplayNode {
self.currentTheme = currentTheme
}
} else {
if theme != self.theme {
if theme != self.theme || accentColor != self.accentColor {
self.imageNode.setSignal(themeIconImage(account: context.account, accountManager: context.sharedContext.accountManager, theme: theme, accentColor: accentColor))
self.theme = theme
self.accentColor = accentColor
}
}
if self.currentTheme == nil || currentTheme !== self.currentTheme! || bordered != self.bordered || selected != self.selected {

View File

@@ -169,7 +169,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
})
}
if false, let liveLocationManager = context.liveLocationManager {
if let liveLocationManager = context.liveLocationManager {
switch locationBroadcastPanelSource {
case .none:
self.locationBroadcastMode = nil

View File

@@ -479,7 +479,7 @@ func fetchRemoteMessage(postbox: Postbox, source: FetchMessageHistoryHoleSource,
var renderedMessages: [Message] = []
for message in messages {
if let message = StoreMessage(apiMessage: message), case let .Id(updatedId) = message.id {
if let message = StoreMessage(apiMessage: message, namespace: id.namespace), case let .Id(updatedId) = message.id {
var addedExisting = false
if transaction.getMessage(updatedId) != nil {
transaction.updateMessage(updatedId, update: { _ in

View File

@@ -246,6 +246,8 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
mainMedia = webpage.file ?? webpage.image
}
let themeMimeType = "application/x-tgtheme-ios"
if let file = mainMedia as? TelegramMediaFile, webpage.type != "telegram_theme" {
if let embedUrl = webpage.embedUrl, !embedUrl.isEmpty {
if automaticPlayback {
@@ -295,10 +297,13 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
}
} else if type == "telegram_theme" {
var file: TelegramMediaFile?
let mimeType = "application/x-tgtheme-ios"
if let contentFiles = webpage.files, let filteredFile = contentFiles.filter({ $0.mimeType == mimeType }).first {
file = filteredFile
} else if let contentFile = webpage.file, contentFile.mimeType == mimeType {
if let contentFiles = webpage.files {
if let filteredFile = contentFiles.filter({ $0.mimeType == themeMimeType }).first {
file = filteredFile
} else {
file = contentFiles.first
}
} else if let contentFile = webpage.file {
file = contentFile
}
if let file = file {
@@ -332,9 +337,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
case "telegram_theme":
title = item.presentationData.strings.Conversation_Theme
text = nil
if mediaAndFlags != nil {
actionTitle = item.presentationData.strings.Conversation_ViewTheme
}
actionTitle = item.presentationData.strings.Conversation_ViewTheme
default:
break
}

View File

@@ -16,6 +16,7 @@ import StickerPackPreviewUI
import PeerAvatarGalleryUI
import PeerInfoUI
import SettingsUI
import AlertUI
private enum ChatMessageGalleryControllerData {
case url(String)
@@ -475,6 +476,9 @@ func openChatTheme(context: AccountContext, message: Message, present: @escaping
let controller = ThemePreviewController(context: context, previewTheme: theme, source: .slug(slug, file))
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
} else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
present(textAlertController(context: context, title: nil, text: presentationData.strings.Theme_Unsupported, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
})
}

View File

@@ -288,6 +288,14 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
present(previewController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
}, error: { [weak controller] error in
let errorText: String
switch error {
case .generic, .slugInvalid:
errorText = presentationData.strings.Theme_ErrorNotFound
case .unsupported:
errorText = presentationData.strings.Theme_Unsupported
}
present(textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
controller?.dismiss()
})
dismissInput()

View File

@@ -728,11 +728,12 @@ public func drawThemeImage(context c: CGContext, theme: PresentationTheme, wallp
}
public func themeImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let isSupportedTheme = fileReference.media.mimeType == "application/x-tgtheme-ios"
let maybeFetched = accountManager.mediaBox.resourceData(fileReference.media.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
let data = maybeFetched
|> take(1)
|> mapToSignal { maybeData -> Signal<(Data?, Data?), NoError> in
if maybeData.complete {
if maybeData.complete && isSupportedTheme {
let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
return .single((loadedData, nil))
} else {
@@ -769,25 +770,30 @@ public func themeImage(account: Account, accountManager: AccountManager, fileRef
}
let reference = fileReference.resourceReference(fileReference.media.resource)
let fullSizeData = Signal<Data?, NoError> { subscriber in
let fetch = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: reference).start()
let disposable = (account.postbox.mediaBox.resourceData(reference.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: false)
|> map { data -> Data? in
return data.complete ? try? Data(contentsOf: URL(fileURLWithPath: data.path)) : nil
}).start(next: { next in
if let data = next {
accountManager.mediaBox.storeResourceData(reference.resource.id, data: data)
}
subscriber.putNext(next)
}, error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
})
return ActionDisposable {
fetch.dispose()
disposable.dispose()
let fullSizeData: Signal<Data?, NoError>
if isSupportedTheme {
fullSizeData = Signal { subscriber in
let fetch = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: reference).start()
let disposable = (account.postbox.mediaBox.resourceData(reference.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: false)
|> map { data -> Data? in
return data.complete ? try? Data(contentsOf: URL(fileURLWithPath: data.path)) : nil
}).start(next: { next in
if let data = next {
accountManager.mediaBox.storeResourceData(reference.resource.id, data: data)
}
subscriber.putNext(next)
}, error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
})
return ActionDisposable {
fetch.dispose()
disposable.dispose()
}
}
} else {
fullSizeData = .single(nil)
}
return thumbnailData |> mapToSignal { thumbnailData in