no message

This commit is contained in:
Ilya Laktyushin 2018-09-25 12:29:53 +01:00
parent f3c966308f
commit 92d95510c7
17 changed files with 212 additions and 193 deletions

View File

@ -738,7 +738,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin
case let .url(url):
var cleanUrl = url
var canAddToReadingList = true
let canOpenIn = !availableOpenInOptions(applicationContext: strongSelf.account.telegramApplicationContext, item: .url(url: url)).isEmpty
let canOpenIn = availableOpenInOptions(applicationContext: strongSelf.account.telegramApplicationContext, item: .url(url: url)).count > 1
let mailtoString = "mailto:"
let telString = "tel:"
var openText = strongSelf.presentationData.strings.Conversation_LinkDialogOpen

View File

@ -511,19 +511,41 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
break
}
}
if messages.count == 1 {
var subject: ShareControllerSubject = ShareControllerSubject.messages(messages)
for m in messages[0].media {
if let image = m as? TelegramMediaImage {
subject = .image(image.representations)
} else if let webpage = m as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if let file = content.file {
subject = .media(.webPage(webPage: WebpageReference(webpage), media: file))
preferredAction = .saveToCameraRoll
} else if let image = content.image {
subject = .media(.webPage(webPage: WebpageReference(webpage), media: image))
preferredAction = .saveToCameraRoll
if content.embedType == "iframe" {
let item = OpenInItem.url(url: content.url)
if availableOpenInOptions(applicationContext: strongSelf.account.telegramApplicationContext, item: item).count > 1 {
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Conversation_FileOpenIn, action: { [weak self] in
if let strongSelf = self {
let openInController = OpenInActionSheetController(postbox: strongSelf.account.postbox, applicationContext: strongSelf.account.telegramApplicationContext, theme: presentationData.theme, strings: presentationData.strings, item: item, additionalAction: nil, openUrl: { [weak self] url in
if let strongSelf = self, let applicationContext = strongSelf.account.applicationContext as? TelegramApplicationContext {
openExternalUrl(account: strongSelf.account, url: url, presentationData: presentationData, applicationContext: applicationContext, navigationController: nil, dismissInput: {})
}
})
strongSelf.controllerInteraction?.presentController(openInController, nil)
}
}))
} else {
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Web_OpenExternal, action: { [weak self] in
if let strongSelf = self {
openExternalUrl(account: strongSelf.account, url: content.url, presentationData: presentationData, applicationContext: strongSelf.account.telegramApplicationContext, navigationController: nil, dismissInput: {})
}
}))
}
} else {
if let file = content.file {
subject = .media(.webPage(webPage: WebpageReference(webpage), media: file))
preferredAction = .saveToCameraRoll
} else if let image = content.image {
subject = .media(.webPage(webPage: WebpageReference(webpage), media: image))
preferredAction = .saveToCameraRoll
}
}
} else if let file = m as? TelegramMediaFile {
subject = .media(.message(message: MessageReference(messages[0]), media: file))

View File

@ -219,7 +219,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
switch action {
case let .url(url):
var cleanUrl = url
let canOpenIn = !availableOpenInOptions(applicationContext: strongSelf.account.telegramApplicationContext, item: .url(url: url)).isEmpty
let canOpenIn = availableOpenInOptions(applicationContext: strongSelf.account.telegramApplicationContext, item: .url(url: url)).count > 1
var canAddToReadingList = true
let mailtoString = "mailto:"
let telString = "tel:"

View File

@ -6,12 +6,13 @@ final class GenericEmbedImplementation: WebEmbedImplementation {
private var evalImpl: ((String) -> Void)?
private var updateStatus: ((MediaPlayerStatus) -> Void)?
private var onPlaybackStarted: (() -> Void)?
private var status : MediaPlayerStatus
private let url: String
init(url: String) {
self.url = url
//self.status = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .buffering(initial: true, whilePlaying: true))
self.status = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .buffering(initial: true, whilePlaying: true))
}
func setup(_ webView: WKWebView, userContentController: WKUserContentController, evaluateJavaScript: @escaping (String) -> Void, updateStatus: @escaping (MediaPlayerStatus) -> Void, onPlaybackStarted: @escaping () -> Void) {
@ -38,7 +39,7 @@ final class GenericEmbedImplementation: WebEmbedImplementation {
self.evalImpl = evaluateJavaScript
self.updateStatus = updateStatus
self.onPlaybackStarted = onPlaybackStarted
//updateStatus(self.status)
updateStatus(self.status)
let html = String(format: htmlTemplate, self.url)
webView.loadHTMLString(html, baseURL: URL(string: "about:blank"))
@ -59,6 +60,9 @@ final class GenericEmbedImplementation: WebEmbedImplementation {
}
func pageReady() {
self.status = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .playing)
self.updateStatus?(self.status)
if let onPlaybackStarted = self.onPlaybackStarted {
onPlaybackStarted()
}

View File

@ -1932,7 +1932,7 @@ func handlePeerInfoAboutTextAction(account: Account, peerId: PeerId, navigateDis
case .longTap:
switch itemLink {
case let .url(url):
let canOpenIn = !availableOpenInOptions(applicationContext: account.telegramApplicationContext, item: .url(url: url)).isEmpty
let canOpenIn = availableOpenInOptions(applicationContext: account.telegramApplicationContext, item: .url(url: url)).count > 1
let openText = canOpenIn ? presentationData.strings.Conversation_FileOpenIn : presentationData.strings.Conversation_LinkDialogOpen
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [

View File

@ -611,7 +611,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
case .longTap:
if let url = self.urlForTapLocation(location) {
let canOpenIn = !availableOpenInOptions(applicationContext: self.account.telegramApplicationContext, item: .url(url: url.url)).isEmpty
let canOpenIn = availableOpenInOptions(applicationContext: self.account.telegramApplicationContext, item: .url(url: url.url)).count > 1
let openText = canOpenIn ? self.strings.Conversation_FileOpenIn : self.strings.Conversation_LinkDialogOpen
let actionSheet = ActionSheetController(presentationTheme: self.presentationTheme)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [

View File

@ -190,7 +190,7 @@ public class PeerMediaCollectionController: TelegramController {
if let strongSelf = self {
switch content {
case let .url(url):
let canOpenIn = !availableOpenInOptions(applicationContext: strongSelf.account.telegramApplicationContext, item: .url(url: url)).isEmpty
let canOpenIn = availableOpenInOptions(applicationContext: strongSelf.account.telegramApplicationContext, item: .url(url: url)).count > 1
let openText = canOpenIn ? strongSelf.presentationData.strings.Conversation_FileOpenIn : strongSelf.presentationData.strings.Conversation_LinkDialogOpen
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [

View File

@ -568,7 +568,7 @@ final class SecureIdAuthController: ViewController {
switch self.state {
case let .form(form):
if case let .form(reqForm) = self.mode, let encryptedFormData = form.encryptedFormData, let formData = form.formData {
let values = parseRequestedFormFields(formData.requestedFields, values: formData.values).map({ $0.1 }).flatMap({ $0 })
let values = parseRequestedFormFields(formData.requestedFields, values: formData.values, primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry).map({ $0.1 }).flatMap({ $0 })
let _ = (grantSecureIdAccess(network: self.account.network, peerId: encryptedFormData.servicePeer.id, publicKey: reqForm.publicKey, scope: reqForm.scope, opaquePayload: reqForm.opaquePayload, opaqueNonce: reqForm.opaqueNonce, values: values, requestedFields: formData.requestedFields)
|> deliverOnMainQueue).start(completed: { [weak self] in

View File

@ -51,11 +51,11 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
self.backgroundColor = presentationData.theme.list.blocksBackgroundColor
self.acceptNode.pressed = { [weak self] in
guard let strongSelf = self, let state = strongSelf.state, case let .form(form) = state, let formData = form.formData else {
guard let strongSelf = self, let state = strongSelf.state, case let .form(form) = state, let encryptedFormData = form.encryptedFormData, let formData = form.formData else {
return
}
for (field, _, filled) in parseRequestedFormFields(formData.requestedFields, values: formData.values) {
for (field, _, filled) in parseRequestedFormFields(formData.requestedFields, values: formData.values, primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry) {
if !filled {
if let contentNode = strongSelf.contentNode as? SecureIdAuthFormContentNode {
if let rect = contentNode.frameForField(field) {
@ -291,7 +291,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
current.updateValues(formData.values)
contentNode = current
} else {
let current = SecureIdAuthFormContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, peer: encryptedFormData.servicePeer, privacyPolicyUrl: encryptedFormData.form.termsUrl, form: formData, openField: { [weak self] field in
let current = SecureIdAuthFormContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, peer: encryptedFormData.servicePeer, privacyPolicyUrl: encryptedFormData.form.termsUrl, form: formData, primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, openField: { [weak self] field in
if let strongSelf = self {
switch field {
case .identity, .address:
@ -438,15 +438,6 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
}
}
let hasErrors: (SecureIdValueWithContext, SecureIdValueKey) -> Bool = { value, key in
for error in value.errors {
if case let .value(valueKey) = error.key, valueKey != key {
return value.errors.count > 1
}
}
return !value.errors.isEmpty
}
switch field {
case let .identity(personalDetails, document, selfie, translations):
if let document = document {
@ -468,11 +459,8 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
}
}
case let .oneOf(types):
var chosenByError = false
outer: for type in types {
inner: for type in types.sorted(by: { $0.valueKey.rawValue < $1.valueKey.rawValue }) {
if let value = findValue(formData.values, key: type.valueKey)?.1 {
let hasErrors = hasErrors(value, type.valueKey)
let data = extractSecureIdValueAdditionalData(value.value)
var dataFilled = true
if selfie && !data.selfie {
@ -481,9 +469,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
if translations && !data.translation {
dataFilled = false
}
if hasValueType == nil || ((hasErrors || dataFilled) && !chosenByError) {
chosenByError = hasErrors
if hasValueType == nil || dataFilled {
switch value.value {
case .passport:
hasValueType = .passport
@ -496,6 +482,10 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
default:
break
}
if dataFilled {
break inner
}
}
}
}
@ -539,41 +529,36 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
}
}
case let .oneOf(types):
var chosenByError = false
outer: for type in types {
inner: for type in types.sorted(by: { $0.valueKey.rawValue < $1.valueKey.rawValue }) {
if let value = findValue(formData.values, key: type.valueKey)?.1 {
let hasErrors = hasErrors(value, type.valueKey)
let data = extractSecureIdValueAdditionalData(value.value)
var dataFilled = true
if translation && !data.translation {
dataFilled = false
}
if hasValueType == nil || ((hasErrors || dataFilled) && !chosenByError) {
chosenByError = hasErrors
if hasValueType == nil || dataFilled {
switch value.value {
case .utilityBill:
hasValueType = .utilityBill
break outer
case .bankStatement:
hasValueType = .bankStatement
break outer
case .rentalAgreement:
hasValueType = .rentalAgreement
break outer
case .passportRegistration:
hasValueType = .passportRegistration
break outer
case .temporaryRegistration:
hasValueType = .temporaryRegistration
break outer
default:
break
}
if dataFilled {
break inner
}
}
}
}
}
}
if let hasValueType = hasValueType {
self.interaction.present(SecureIdDocumentFormController(account: self.account, context: context, requestedData: .address(details: addressDetails, document: hasValueType, translations: translation), primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, values: formData.values, updatedValues: { values in

View File

@ -9,6 +9,7 @@ private let passwordFont = Font.regular(16.0)
private let buttonFont = Font.regular(17.0)
final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode, UITextFieldDelegate {
private let primaryLanguageByCountry: [String: String]
private let requestedFields: [SecureIdRequestedFormField]
private let fieldBackgroundNode: ASDisplayNode
private let fieldNodes: [SecureIdAuthFormFieldNode]
@ -18,9 +19,10 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode,
private let requestLayout: () -> Void
private var validLayout: CGFloat?
init(theme: PresentationTheme, strings: PresentationStrings, peer: Peer, privacyPolicyUrl: String?, form: SecureIdForm, openField: @escaping (SecureIdParsedRequestedFormField) -> Void, openURL: @escaping (String) -> Void, openMention: @escaping (TelegramPeerMention) -> Void, requestLayout: @escaping () -> Void) {
init(theme: PresentationTheme, strings: PresentationStrings, peer: Peer, privacyPolicyUrl: String?, form: SecureIdForm, primaryLanguageByCountry: [String: String], openField: @escaping (SecureIdParsedRequestedFormField) -> Void, openURL: @escaping (String) -> Void, openMention: @escaping (TelegramPeerMention) -> Void, requestLayout: @escaping () -> Void) {
self.requestLayout = requestLayout
self.primaryLanguageByCountry = primaryLanguageByCountry
self.requestedFields = form.requestedFields
self.fieldBackgroundNode = ASDisplayNode()
self.fieldBackgroundNode.isLayerBacked = true
@ -28,8 +30,8 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode,
var fieldNodes: [SecureIdAuthFormFieldNode] = []
for (field, fieldValues, _) in parseRequestedFormFields(self.requestedFields, values: form.values) {
fieldNodes.append(SecureIdAuthFormFieldNode(theme: theme, strings: strings, field: field, values: fieldValues, selected: {
for (field, fieldValues, _) in parseRequestedFormFields(self.requestedFields, values: form.values, primaryLanguageByCountry: primaryLanguageByCountry) {
fieldNodes.append(SecureIdAuthFormFieldNode(theme: theme, strings: strings, field: field, values: fieldValues, primaryLanguageByCountry: primaryLanguageByCountry, selected: {
openField(field)
}))
}
@ -88,9 +90,9 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode,
func updateValues(_ values: [SecureIdValueWithContext]) {
var index = 0
for (_, fieldValues, _) in parseRequestedFormFields(self.requestedFields, values: values) {
for (_, fieldValues, _) in parseRequestedFormFields(self.requestedFields, values: values, primaryLanguageByCountry: self.primaryLanguageByCountry) {
if index < self.fieldNodes.count {
self.fieldNodes[index].updateValues(fieldValues)
self.fieldNodes[index].updateValues(fieldValues, primaryLanguageByCountry: self.primaryLanguageByCountry)
}
index += 1
}

View File

@ -25,24 +25,24 @@ enum SecureIdRequestedIdentityDocument: Int32 {
}
enum SecureIdRequestedAddressDocument: Int32 {
case utilityBill
case bankStatement
case rentalAgreement
case passportRegistration
case temporaryRegistration
case bankStatement
case utilityBill
case rentalAgreement
var valueKey: SecureIdValueKey {
switch self {
case .utilityBill:
return .utilityBill
case .bankStatement:
return .bankStatement
case .rentalAgreement:
return .rentalAgreement
case .passportRegistration:
return .passportRegistration
case .temporaryRegistration:
return .temporaryRegistration
case .bankStatement:
return .bankStatement
case .utilityBill:
return .utilityBill
case .rentalAgreement:
return .rentalAgreement
}
}
}
@ -110,7 +110,7 @@ private struct RequestedFieldValues {
}
}
func parseRequestedFormFields(_ types: [SecureIdRequestedFormField], values: [SecureIdValueWithContext]) -> [(SecureIdParsedRequestedFormField, [SecureIdValueWithContext], Bool)] {
func parseRequestedFormFields(_ types: [SecureIdRequestedFormField], values: [SecureIdValueWithContext], primaryLanguageByCountry: [String: String]) -> [(SecureIdParsedRequestedFormField, [SecureIdValueWithContext], Bool)] {
var requestedValues = RequestedFieldValues()
for type in types {
@ -191,20 +191,28 @@ func parseRequestedFormFields(_ types: [SecureIdRequestedFormField], values: [Se
}
return result.map { field in
let (fieldValues, filled) = findValuesForField(field: field, values: values)
let (fieldValues, filled) = findValuesForField(field: field, values: values, primaryLanguageByCountry: primaryLanguageByCountry)
return (field, fieldValues, filled)
}
}
private func findValuesForField(field: SecureIdParsedRequestedFormField, values: [SecureIdValueWithContext]) -> ([SecureIdValueWithContext], Bool) {
private func findValuesForField(field: SecureIdParsedRequestedFormField, values: [SecureIdValueWithContext], primaryLanguageByCountry: [String: String]) -> ([SecureIdValueWithContext], Bool) {
switch field {
case let .identity(personalDetails, document, selfie, translation):
var filled = true
var result: [SecureIdValueWithContext] = []
if personalDetails != nil {
if let personalDetails = personalDetails {
if let value = findValue(values, key: .personalDetails)?.1 {
result.append(value)
if !value.errors.isEmpty {
if case let .personalDetails(value) = value.value {
let hasNativeNames = value.nativeName?.isComplete() ?? false
let requiresNativeNames = primaryLanguageByCountry[value.residenceCountryCode] != "en"
if personalDetails.nativeNames && !hasNativeNames && requiresNativeNames {
filled = false
}
}
if errorForErrorKey(.personalDetails, value) != nil {
filled = false
}
} else {
@ -223,44 +231,44 @@ private func findValuesForField(field: SecureIdParsedRequestedFormField, values:
if translation && !data.translation {
filled = false
}
if !value.errors.isEmpty {
if errorForErrorKey(type.valueKey, value) != nil {
filled = false
}
} else {
filled = false
}
case let .oneOf(types):
var anyDocument = false
var bestMatchingValue: SecureIdValueWithContext?
inner: for type in types {
if let value = findValue(values, key: type.valueKey)?.1 {
if bestMatchingValue == nil {
bestMatchingValue = value
}
let data = extractSecureIdValueAdditionalData(value.value)
var dataFilled = true
if selfie && !data.selfie {
dataFilled = false
}
if translation && !data.translation {
dataFilled = false
}
if dataFilled {
bestMatchingValue = value
anyDocument = true
break inner
case let .oneOf(types):
var anyDocument = false
var bestMatchingValue: SecureIdValueWithContext?
inner: for type in types.sorted(by: { $0.valueKey.rawValue < $1.valueKey.rawValue }) {
if let value = findValue(values, key: type.valueKey)?.1 {
if bestMatchingValue == nil {
bestMatchingValue = value
}
let data = extractSecureIdValueAdditionalData(value.value)
var dataFilled = true
if selfie && !data.selfie {
dataFilled = false
}
if translation && !data.translation {
dataFilled = false
}
if dataFilled {
bestMatchingValue = value
anyDocument = true
break inner
}
}
}
}
if !anyDocument {
filled = false
}
if let bestMatchingValue = bestMatchingValue {
result.append(bestMatchingValue)
if !bestMatchingValue.errors.isEmpty {
if !anyDocument {
filled = false
}
}
if let bestMatchingValue = bestMatchingValue {
result.append(bestMatchingValue)
if errorForErrorKey(bestMatchingValue.value.key, bestMatchingValue) != nil {
filled = false
}
}
}
}
return (result, filled)
@ -270,7 +278,7 @@ private func findValuesForField(field: SecureIdParsedRequestedFormField, values:
if addressDetails {
if let value = findValue(values, key: .address)?.1 {
result.append(value)
if !value.errors.isEmpty {
if errorForErrorKey(.address, value) != nil {
filled = false
}
} else {
@ -286,7 +294,7 @@ private func findValuesForField(field: SecureIdParsedRequestedFormField, values:
if translation && !data.translation {
filled = false
}
if !value.errors.isEmpty {
if errorForErrorKey(type.valueKey, value) != nil {
filled = false
}
} else {
@ -295,7 +303,7 @@ private func findValuesForField(field: SecureIdParsedRequestedFormField, values:
case let .oneOf(types):
var anyDocument = false
var bestMatchingValue: SecureIdValueWithContext?
inner: for type in types {
inner: for type in types.sorted(by: { $0.valueKey.rawValue < $1.valueKey.rawValue }) {
if let value = findValue(values, key: type.valueKey)?.1 {
if bestMatchingValue == nil {
bestMatchingValue = value
@ -317,7 +325,7 @@ private func findValuesForField(field: SecureIdParsedRequestedFormField, values:
}
if let bestMatchingValue = bestMatchingValue {
result.append(bestMatchingValue)
if !bestMatchingValue.errors.isEmpty {
if errorForErrorKey(bestMatchingValue.value.key, bestMatchingValue) != nil {
filled = false
}
}
@ -587,7 +595,7 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
title = strings.Passport_FieldOneOf_Or(stringForDocumentType(typesArray[0], strings: strings), stringForDocumentType(typesArray[1], strings: strings)).0
}
placeholder = placeholderForDocumentTypes(typesArray, strings: strings)
for type in types {
for type in types.sorted(by: { $0.valueKey.rawValue < $1.valueKey.rawValue }) {
if let value = findValue(values, key: type.valueKey)?.1.value {
filledDocument = (type, value)
break
@ -638,7 +646,7 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
title = strings.Passport_FieldOneOf_Or(stringForDocumentType(typesArray[0], strings: strings), stringForDocumentType(typesArray[1], strings: strings)).0
}
placeholder = placeholderForDocumentTypes(typesArray, strings: strings)
for type in types {
for type in types.sorted(by: { $0.valueKey.rawValue < $1.valueKey.rawValue }) {
if let value = findValue(values, key: type.valueKey)?.1.value {
filledDocument = (type, value)
break
@ -691,76 +699,51 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings:
return (title, text.isEmpty ? placeholder : text)
}
private func errorForErrorKey(_ key: SecureIdValueKey, _ value: SecureIdValueWithContext) -> String? {
if let error = value.errors[.value(key)] {
return error
} else if let error = value.errors.first {
if case .value = error.key {} else {
return error.value
}
}
return nil
}
private func fieldErrorText(field: SecureIdParsedRequestedFormField, values: [SecureIdValueWithContext]) -> String? {
switch field {
case let .identity(personalDetails, document, _, _):
if let _ = personalDetails, let value = findValue(values, key: .personalDetails)?.1 {
if let error = value.errors[.value(.personalDetails)] {
return error
} else if let error = value.errors.first {
if case .value = error.key {} else {
return error.value
}
}
if let _ = personalDetails, let value = findValue(values, key: .personalDetails)?.1, let error = errorForErrorKey(.personalDetails, value) {
return error
}
if let document = document {
switch document {
case let .just(type):
if let value = findValue(values, key: type.valueKey)?.1 {
if let error = value.errors[.value(type.valueKey)] {
return error
} else if let error = value.errors.first {
if case .value = error.key {} else {
return error.value
}
}
if let value = findValue(values, key: type.valueKey)?.1, let error = errorForErrorKey(type.valueKey, value) {
return error
}
case let .oneOf(types):
for type in types {
if let value = findValue(values, key: type.valueKey)?.1 {
if let error = value.errors[.value(type.valueKey)] {
return error
} else if let error = value.errors.first {
if case .value = error.key {} else {
return error.value
}
}
for type in types.sorted(by: { $0.valueKey.rawValue < $1.valueKey.rawValue }) {
if let value = findValue(values, key: type.valueKey)?.1, let error = errorForErrorKey(type.valueKey, value) {
return error
}
}
}
}
case let .address(addressDetails, document, _):
if addressDetails, let value = findValue(values, key: .address)?.1 {
if let error = value.errors[.value(.address)] {
return error
} else if let error = value.errors.first {
if case .value = error.key {} else {
return error.value
}
}
if addressDetails, let value = findValue(values, key: .address)?.1, let error = errorForErrorKey(.address, value) {
return error
}
if let document = document {
switch document {
case let .just(type):
if let value = findValue(values, key: type.valueKey)?.1 {
if let error = value.errors[.value(type.valueKey)] {
return error
} else if let error = value.errors.first {
if case .value = error.key {} else {
return error.value
}
}
if let value = findValue(values, key: type.valueKey)?.1, let error = errorForErrorKey(type.valueKey, value) {
return error
}
case let .oneOf(types):
for type in types {
if let value = findValue(values, key: type.valueKey)?.1 {
if let error = value.errors[.value(type.valueKey)] {
return error
} else if let error = value.errors.first {
if case .value = error.key {} else {
return error.value
}
}
for type in types.sorted(by: { $0.valueKey.rawValue < $1.valueKey.rawValue }) {
if let value = findValue(values, key: type.valueKey)?.1, let error = errorForErrorKey(type.valueKey, value) {
return error
}
}
}
@ -791,7 +774,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
private let theme: PresentationTheme
private let strings: PresentationStrings
init(theme: PresentationTheme, strings: PresentationStrings, field: SecureIdParsedRequestedFormField, values: [SecureIdValueWithContext], selected: @escaping () -> Void) {
init(theme: PresentationTheme, strings: PresentationStrings, field: SecureIdParsedRequestedFormField, values: [SecureIdValueWithContext], primaryLanguageByCountry: [String: String], selected: @escaping () -> Void) {
self.field = field
self.theme = theme
self.strings = strings
@ -845,7 +828,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
self.addSubnode(self.checkNode)
self.addSubnode(self.buttonNode)
self.updateValues(values)
self.updateValues(values, primaryLanguageByCountry: primaryLanguageByCountry)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
@ -862,7 +845,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
func updateValues(_ values: [SecureIdValueWithContext]) {
func updateValues(_ values: [SecureIdValueWithContext], primaryLanguageByCountry: [String: String]) {
var (title, text) = fieldTitleAndText(field: self.field, strings: self.strings, values: values)
var textColor = self.theme.list.itemSecondaryTextColor
@ -877,10 +860,13 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode {
case let .identity(personalDetails, document, selfie, translation):
if let personalDetails = personalDetails {
if let value = findValue(values, key: .personalDetails)?.1 {
let data = extractSecureIdValueAdditionalData(value.value)
if personalDetails.nativeNames && !data.nativeNames {
filled = false
text = strings.Passport_FieldIdentityDetailsHelp
if case let .personalDetails(value) = value.value {
let hasNativeNames = value.nativeName?.isComplete() ?? false
let requiresNativeNames = primaryLanguageByCountry[value.residenceCountryCode] != "en"
if personalDetails.nativeNames && !hasNativeNames && requiresNativeNames {
filled = false
text = strings.Passport_FieldIdentityDetailsHelp
}
}
} else {
filled = false

View File

@ -248,11 +248,14 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.statusButtonNode.isHidden = true
}
var disablePlayerControls = false
var isAnimated = false
if let content = item.content as? NativeVideoContent {
isAnimated = content.fileReference.media.isAnimated
} else if let _ = item.content as? SystemVideoContent {
self._title.set(.single(item.presentationData.strings.Message_Video))
} else if let content = item.content as? WebEmbedVideoContent, case .iframe = webEmbedType(content: content.webpageContent) {
disablePlayerControls = true
}
if let videoNode = self.videoNode {
@ -260,7 +263,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
videoNode.removeFromSupernode()
}
if isAnimated {
if isAnimated || disablePlayerControls {
self.footerContentNode.scrubberView = nil
}
@ -273,7 +276,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
self.videoNode = videoNode
videoNode.isUserInteractionEnabled = false
videoNode.isUserInteractionEnabled = disablePlayerControls
videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335)
videoNode.canAttachContent = true
self.updateDisplayPlaceholder(!videoNode.ownsContentNode)
@ -340,7 +343,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
strongSelf.statusButtonNode.isHidden = !initialBuffering && (strongSelf.didPause || !isPaused || value == nil)
}
if isAnimated {
if isAnimated || disablePlayerControls {
strongSelf.footerContentNode.content = .info
}
else if isPaused {
@ -357,7 +360,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.zoomableContent = (videoSize, videoNode)
if !isAnimated {
if !isAnimated && !disablePlayerControls {
let rightBarButtonItem = UIBarButtonItem(image: pictureInPictureButtonImage, style: .plain, target: self, action: #selector(self.pictureInPictureButtonPressed))
self._rightBarButtonItem.set(.single(rightBarButtonItem))
}

View File

@ -7,8 +7,9 @@ func extractVimeoVideoIdAndTimestamp(url: String) -> (String, Int)? {
return nil
}
let domain = "player.vimeo.com"
let match = host == domain || host.contains(".\(domain)")
let match = ["vimeo.com", "player.vimeo.com"].contains(where: { (domain) -> Bool in
return host == domain || host.contains(".\(domain)")
})
guard match else {
return nil
@ -39,7 +40,7 @@ func extractVimeoVideoIdAndTimestamp(url: String) -> (String, Int)? {
var nextComponentIsVideoId = false
for component in pathComponents {
if nextComponentIsVideoId {
if CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: component)) || nextComponentIsVideoId {
videoId = component
break
} else if component == "video" {

View File

@ -2,6 +2,7 @@ import Foundation
import AsyncDisplayKit
import SwiftSignalKit
import WebKit
import TelegramCore
protocol WebEmbedImplementation {
func setup(_ webView: WKWebView, userContentController: WKUserContentController, evaluateJavaScript: @escaping (String) -> Void, updateStatus: @escaping (MediaPlayerStatus) -> Void, onPlaybackStarted: @escaping () -> Void)
@ -15,14 +16,31 @@ protocol WebEmbedImplementation {
func callback(url: URL)
}
func webEmbedImplementation(embedUrl: String, url: String) -> WebEmbedImplementation {
if let (videoId, timestamp) = extractYoutubeVideoIdAndTimestamp(url: url) {
return YoutubeEmbedImplementation(videoId: videoId, timestamp: timestamp)
} else if let (videoId, timestamp) = extractVimeoVideoIdAndTimestamp(url: url) {
return VimeoEmbedImplementation(videoId: videoId, timestamp: timestamp)
enum WebEmbedType {
case youtube(videoId: String, timestamp: Int)
case vimeo(videoId: String, timestamp: Int)
case iframe(url: String)
}
func webEmbedType(content: TelegramMediaWebpageLoadedContent) -> WebEmbedType {
if let (videoId, timestamp) = extractYoutubeVideoIdAndTimestamp(url: content.url) {
return .youtube(videoId: videoId, timestamp: timestamp)
} else if let (videoId, timestamp) = extractVimeoVideoIdAndTimestamp(url: content.url) {
return .vimeo(videoId: videoId, timestamp: timestamp)
} else {
return .iframe(url: content.embedUrl ?? content.url)
}
}
func webEmbedImplementation(for type: WebEmbedType) -> WebEmbedImplementation {
switch type {
case let .youtube(videoId, timestamp):
return YoutubeEmbedImplementation(videoId: videoId, timestamp: timestamp)
case let .vimeo(videoId, timestamp):
return VimeoEmbedImplementation(videoId: videoId, timestamp: timestamp)
case let .iframe(url):
return GenericEmbedImplementation(url: url)
}
return GenericEmbedImplementation(url: url)
}
final class WebEmbedPlayerNode: ASDisplayNode, WKNavigationDelegate {
@ -84,17 +102,11 @@ final class WebEmbedPlayerNode: ASDisplayNode, WKNavigationDelegate {
self.view.addSubview(self.webView)
self.impl.setup(self.webView, userContentController: userContentController, evaluateJavaScript: { [weak self] js in
if let strongSelf = self {
strongSelf.evaluateJavaScript(js: js)
}
self?.evaluateJavaScript(js: js)
}, updateStatus: { [weak self] status in
if let strongSelf = self {
strongSelf.statusValue.set(status)
}
self?.statusValue.set(status)
}, onPlaybackStarted: { [weak self] in
if let strongSelf = self {
strongSelf.readyValue.set(true)
}
self?.readyValue.set(true)
})
}

View File

@ -77,14 +77,9 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte
self.imageNode = TransformImageNode()
let embedImpl: WebEmbedImplementation
if let embedUrl = webpageContent.embedUrl {
embedImpl = webEmbedImplementation(embedUrl: embedUrl, url: webpageContent.url)
self.playerNode = WebEmbedPlayerNode(impl: embedImpl, intrinsicDimensions: self.intrinsicDimensions)
} else {
embedImpl = GenericEmbedImplementation(url: webpageContent.url)
self.playerNode = WebEmbedPlayerNode(impl: embedImpl, intrinsicDimensions: self.intrinsicDimensions)
}
let embedType = webEmbedType(content: webpageContent)
let embedImpl = webEmbedImplementation(for: embedType)
self.playerNode = WebEmbedPlayerNode(impl: embedImpl, intrinsicDimensions: self.intrinsicDimensions)
super.init()

View File

@ -193,9 +193,7 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation {
}
self.status = MediaPlayerStatus(generationTimestamp: self.status.generationTimestamp, duration: self.status.duration, dimensions: self.status.dimensions, timestamp: timestamp, baseRate: 1.0, seekId: self.status.seekId + 1, status: self.status.status)
if let updateStatus = self.updateStatus {
updateStatus(self.status)
}
self.updateStatus?(self.status)
self.ignorePosition = 2
}

View File

@ -25,8 +25,9 @@ typedef enum {
Inch4 = 1,
Inch47 = 2,
Inch55 = 3,
iPad = 4,
iPadPro = 5
Inch65 = 4,
iPad = 5,
iPadPro = 6
} DeviceScreen;
static void TGDispatchOnMainThread(dispatch_block_t block) {
@ -411,6 +412,9 @@ static void TGDispatchOnMainThread(dispatch_block_t block) {
case 667:
deviceScreen = Inch47;
break;
case 896:
deviceScreen = Inch65;
break;
default:
deviceScreen = Inch55;
break;
@ -491,6 +495,13 @@ static void TGDispatchOnMainThread(dispatch_block_t block) {
pageControlY = pageY + 160.0f;
break;
case Inch65:
glViewY = 62 + 85;
startButtonY = 75 + 30;
pageY = 245 + 125;
pageControlY = pageY + 160.0f;
break;
default:
break;
}