mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 11:20:18 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
2179f76fbe
@ -12602,3 +12602,47 @@ Sorry for the inconvenience.";
|
|||||||
"Story.Privacy.ChooseCoverInfo" = "Choose a frame from the story to show in your Profile.";
|
"Story.Privacy.ChooseCoverInfo" = "Choose a frame from the story to show in your Profile.";
|
||||||
"Story.Privacy.ChooseCoverChannelInfo" = "Choose a frame from the story to show in channel profile.";
|
"Story.Privacy.ChooseCoverChannelInfo" = "Choose a frame from the story to show in channel profile.";
|
||||||
"Story.Privacy.ChooseCoverGroupInfo" = "Choose a frame from the story to show in group profile.";
|
"Story.Privacy.ChooseCoverGroupInfo" = "Choose a frame from the story to show in group profile.";
|
||||||
|
|
||||||
|
"ChatList.Search.FilterApps" = "Apps";
|
||||||
|
"ChatList.Search.SectionPopularApps" = "POPULAR APPS";
|
||||||
|
"ChatList.Search.SectionRecentApps" = "APPS YOU USE";
|
||||||
|
"ChatList.Search.Apps.Empty.Text" = "No Apps Found";
|
||||||
|
|
||||||
|
"VoiceChat.MicrophoneModes" = "Microphone Modes";
|
||||||
|
|
||||||
|
"MiniAppList.Title" = "Examples";
|
||||||
|
"MiniAppList.ListSectionHeader" = "APPS THAT ACCEPT STARS";
|
||||||
|
|
||||||
|
"PeerInfo.PaneBotPreviews" = "Preview";
|
||||||
|
"PeerInfo.OpenAppButton" = "Open App";
|
||||||
|
"PeerInfo.AppFooterAdmin" = "By publishing this mini app, you agree to the [Telegram Terms of Service for Developers](https://telegram.org/privacy).";
|
||||||
|
"PeerInfo.AppFooter" = "By launching this mini app, you agree to the [Terms of Service for Mini Apps](https://telegram.org/privacy).";
|
||||||
|
"BotPreviews.MenuAddPreview" = "Add Preview";
|
||||||
|
"BotPreviews.MenuReorder" = "Reorder";
|
||||||
|
"BotPreviews.MenuDeleteLanguage" = "Delete %@";
|
||||||
|
"BotPreviews.SubtitleLoading" = "loading";
|
||||||
|
"BotPreviews.SubtitleEmpty" = "no preview added";
|
||||||
|
"BotPreviews.SubtitleCount_1" = "1 preview";
|
||||||
|
"BotPreviews.SubtitleCount_any" = "1 previews";
|
||||||
|
"BotPreviews.SheetDeleteTitle_1" = "Delete 1 Preview?";
|
||||||
|
"BotPreviews.SheetDeleteTitle_any" = "Delete %d Previews?";
|
||||||
|
"BotPreviews.LanguageTab.Main" = "Main";
|
||||||
|
"BotPreviews.LanguageTab.Add" = "+ Add Language";
|
||||||
|
"BotPreviews.Empty.Title" = "No Preview";
|
||||||
|
"BotPreviews.Empty.Text_1" = "Upload up to 1 screenshot and video demos for your mini app.";
|
||||||
|
"BotPreviews.Empty.Text_any" = "Upload up to %d screenshots and video demos for your mini app.";
|
||||||
|
"BotPreviews.Empty.Add" = "Add Preview";
|
||||||
|
"BotPreviews.Empty.AddTranslation" = "Create a Translation";
|
||||||
|
"BotPreviews.Empty.DeleteTranslation" = "Delete this Translation";
|
||||||
|
"BotPreviews.Empty.Separator" = "or";
|
||||||
|
"BotPreviews.AlertTooManyPreviews_1" = "You can add at most 1 preview.";
|
||||||
|
"BotPreviews.AlertTooManyPreviews_any" = "You can add at most 1 previews.";
|
||||||
|
"BotPreviews.DeleteTranslationAlert.Title" = "Delete Translation";
|
||||||
|
"BotPreviews.DeleteTranslationAlert.Text" = "Are you sure you want to delete this translation?";
|
||||||
|
"BotPreviews.TranslationFooter.Text" = "This preview will be displayed for all users who have %@ set as their language.";
|
||||||
|
"BotPreviews.DefaultFooter.Text" = "This preview will be shown by default. You can also add translations into specific languages.";
|
||||||
|
"BotPreviews.SelectLanguage.Title" = "Add a Translation";
|
||||||
|
"BotPreview.ViewContextDelete" = "Delete Preview";
|
||||||
|
|
||||||
|
"WebBrowser.Download.Confirmation" = "Do you want to download \"%@\"?";
|
||||||
|
"WebBrowser.Download.Download" = "Download";
|
||||||
|
|||||||
@ -46,6 +46,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode",
|
"//submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode",
|
||||||
"//submodules/SearchUI",
|
"//submodules/SearchUI",
|
||||||
"//submodules/SearchBarNode",
|
"//submodules/SearchBarNode",
|
||||||
|
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -15,6 +15,7 @@ final class AddressBarContentComponent: Component {
|
|||||||
|
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
|
let metrics: LayoutMetrics
|
||||||
let url: String
|
let url: String
|
||||||
let isSecure: Bool
|
let isSecure: Bool
|
||||||
let isExpanded: Bool
|
let isExpanded: Bool
|
||||||
@ -23,6 +24,7 @@ final class AddressBarContentComponent: Component {
|
|||||||
init(
|
init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
|
metrics: LayoutMetrics,
|
||||||
url: String,
|
url: String,
|
||||||
isSecure: Bool,
|
isSecure: Bool,
|
||||||
isExpanded: Bool,
|
isExpanded: Bool,
|
||||||
@ -30,6 +32,7 @@ final class AddressBarContentComponent: Component {
|
|||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
|
self.metrics = metrics
|
||||||
self.url = url
|
self.url = url
|
||||||
self.isSecure = isSecure
|
self.isSecure = isSecure
|
||||||
self.isExpanded = isExpanded
|
self.isExpanded = isExpanded
|
||||||
@ -43,6 +46,9 @@ final class AddressBarContentComponent: Component {
|
|||||||
if lhs.strings !== rhs.strings {
|
if lhs.strings !== rhs.strings {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.metrics != rhs.metrics {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.url != rhs.url {
|
if lhs.url != rhs.url {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -78,6 +84,7 @@ final class AddressBarContentComponent: Component {
|
|||||||
var title: String
|
var title: String
|
||||||
var isSecure: Bool
|
var isSecure: Bool
|
||||||
var collapseFraction: CGFloat
|
var collapseFraction: CGFloat
|
||||||
|
var isTablet: Bool
|
||||||
|
|
||||||
static func ==(lhs: Params, rhs: Params) -> Bool {
|
static func ==(lhs: Params, rhs: Params) -> Bool {
|
||||||
if lhs.theme !== rhs.theme {
|
if lhs.theme !== rhs.theme {
|
||||||
@ -101,6 +108,9 @@ final class AddressBarContentComponent: Component {
|
|||||||
if lhs.collapseFraction != rhs.collapseFraction {
|
if lhs.collapseFraction != rhs.collapseFraction {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isTablet != rhs.isTablet {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,9 +216,9 @@ final class AddressBarContentComponent: Component {
|
|||||||
self.activated(true)
|
self.activated(true)
|
||||||
if let textField = self.textField {
|
if let textField = self.textField {
|
||||||
textField.becomeFirstResponder()
|
textField.becomeFirstResponder()
|
||||||
Queue.mainQueue().justDispatch {
|
Queue.mainQueue().after(0.3, {
|
||||||
textField.selectAll(nil)
|
textField.selectAll(nil)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +248,9 @@ final class AddressBarContentComponent: Component {
|
|||||||
|
|
||||||
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
if let component = self.component {
|
if let component = self.component {
|
||||||
component.performAction.invoke(.navigateTo(explicitUrl(textField.text ?? "")))
|
let finalUrl = explicitUrl(textField.text ?? "")
|
||||||
|
// finalUrl = finalUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) ?? finalUrl
|
||||||
|
component.performAction.invoke(.navigateTo(finalUrl))
|
||||||
}
|
}
|
||||||
textField.endEditing(true)
|
textField.endEditing(true)
|
||||||
return false
|
return false
|
||||||
@ -252,7 +264,7 @@ final class AddressBarContentComponent: Component {
|
|||||||
self.placeholderContent.view?.isHidden = !text.isEmpty
|
self.placeholderContent.view?.isHidden = !text.isEmpty
|
||||||
|
|
||||||
if let params = self.params {
|
if let params = self.params {
|
||||||
self.update(theme: params.theme, strings: params.strings, size: params.size, isActive: params.isActive, title: params.title, isSecure: params.isSecure, collapseFraction: params.collapseFraction, transition: .immediate)
|
self.update(theme: params.theme, strings: params.strings, size: params.size, isActive: params.isActive, title: params.title, isSecure: params.isSecure, collapseFraction: params.collapseFraction, isTablet: params.isTablet, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,13 +285,18 @@ final class AddressBarContentComponent: Component {
|
|||||||
var title: String = ""
|
var title: String = ""
|
||||||
if let parsedUrl = URL(string: component.url) {
|
if let parsedUrl = URL(string: component.url) {
|
||||||
title = parsedUrl.host ?? component.url
|
title = parsedUrl.host ?? component.url
|
||||||
|
if title.hasPrefix("www.") {
|
||||||
|
title.removeSubrange(title.startIndex ..< title.index(title.startIndex, offsetBy: 4))
|
||||||
|
}
|
||||||
|
title = title.idnaDecoded ?? title
|
||||||
}
|
}
|
||||||
self.update(theme: component.theme, strings: component.strings, size: availableSize, isActive: isActive, title: title.lowercased(), isSecure: component.isSecure, collapseFraction: collapseFraction, transition: transition)
|
|
||||||
|
self.update(theme: component.theme, strings: component.strings, size: availableSize, isActive: isActive, title: title.lowercased(), isSecure: component.isSecure, collapseFraction: collapseFraction, isTablet: component.metrics.isTablet, transition: transition)
|
||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, isActive: Bool, title: String, isSecure: Bool, collapseFraction: CGFloat, transition: ComponentTransition) {
|
public func update(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, isActive: Bool, title: String, isSecure: Bool, collapseFraction: CGFloat, isTablet: Bool, transition: ComponentTransition) {
|
||||||
let params = Params(
|
let params = Params(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
strings: strings,
|
strings: strings,
|
||||||
@ -287,7 +304,8 @@ final class AddressBarContentComponent: Component {
|
|||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
title: title,
|
title: title,
|
||||||
isSecure: isSecure,
|
isSecure: isSecure,
|
||||||
collapseFraction: collapseFraction
|
collapseFraction: collapseFraction,
|
||||||
|
isTablet: isTablet
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.params == params {
|
if self.params == params {
|
||||||
@ -327,12 +345,13 @@ final class AddressBarContentComponent: Component {
|
|||||||
let cancelButtonSpacing: CGFloat = 8.0
|
let cancelButtonSpacing: CGFloat = 8.0
|
||||||
|
|
||||||
var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight))
|
var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight))
|
||||||
if isActiveWithText {
|
if isActiveWithText && !isTablet {
|
||||||
backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing
|
backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing
|
||||||
}
|
}
|
||||||
transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame)
|
transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame)
|
||||||
|
|
||||||
transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height)))
|
transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height)))
|
||||||
|
self.cancelButton.isUserInteractionEnabled = isActiveWithText && !isTablet
|
||||||
|
|
||||||
let textX: CGFloat = backgroundFrame.minX + sideInset
|
let textX: CGFloat = backgroundFrame.minX + sideInset
|
||||||
let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height))
|
let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height))
|
||||||
@ -410,7 +429,7 @@ final class AddressBarContentComponent: Component {
|
|||||||
cancelButtonTitleComponentView.isUserInteractionEnabled = false
|
cancelButtonTitleComponentView.isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize))
|
transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize))
|
||||||
transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0)
|
transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText && !isTablet ? 1.0 : 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height))
|
let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height))
|
||||||
@ -431,7 +450,25 @@ final class AddressBarContentComponent: Component {
|
|||||||
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.text = self.component?.url ?? ""
|
var address = self.component?.url ?? ""
|
||||||
|
if let components = URLComponents(string: address) {
|
||||||
|
if #available(iOS 16.0, *), let encodedHost = components.encodedHost {
|
||||||
|
if let decodedHost = components.host, encodedHost != decodedHost {
|
||||||
|
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
|
||||||
|
}
|
||||||
|
} else if let encodedHost = components.host {
|
||||||
|
if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost {
|
||||||
|
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if textField.text != address {
|
||||||
|
textField.text = address
|
||||||
|
self.clearIconView.isHidden = address.isEmpty
|
||||||
|
self.clearIconButton.isHidden = address.isEmpty
|
||||||
|
self.placeholderContent.view?.isHidden = !address.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
textField.textColor = theme.rootController.navigationSearchBar.inputTextColor
|
textField.textColor = theme.rootController.navigationSearchBar.inputTextColor
|
||||||
transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideInset, y: backgroundFrame.minY - UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideInset - 32.0, height: backgroundFrame.height)))
|
transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideInset, y: backgroundFrame.minY - UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideInset - 32.0, height: backgroundFrame.height)))
|
||||||
|
|||||||
@ -13,17 +13,20 @@ final class BrowserAddressListComponent: Component {
|
|||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
|
let insets: UIEdgeInsets
|
||||||
let navigateTo: (String) -> Void
|
let navigateTo: (String) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
|
insets: UIEdgeInsets,
|
||||||
navigateTo: @escaping (String) -> Void
|
navigateTo: @escaping (String) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
|
self.insets = insets
|
||||||
self.navigateTo = navigateTo
|
self.navigateTo = navigateTo
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +40,9 @@ final class BrowserAddressListComponent: Component {
|
|||||||
if lhs.strings !== rhs.strings {
|
if lhs.strings !== rhs.strings {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.insets != rhs.insets {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +154,7 @@ final class BrowserAddressListComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
self.endEditing(true)
|
self.window?.endEditing(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateScrolling(transition: ComponentTransition) {
|
private func updateScrolling(transition: ComponentTransition) {
|
||||||
@ -211,6 +217,7 @@ final class BrowserAddressListComponent: Component {
|
|||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
style: .plain,
|
style: .plain,
|
||||||
title: sectionTitle,
|
title: sectionTitle,
|
||||||
|
insets: component.insets,
|
||||||
actionTitle: section.id == 0 ? "Clear" : nil,
|
actionTitle: section.id == 0 ? "Clear" : nil,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
if let self, let component = self.component {
|
if let self, let component = self.component {
|
||||||
@ -292,6 +299,7 @@ final class BrowserAddressListComponent: Component {
|
|||||||
webPage: webPage!,
|
webPage: webPage!,
|
||||||
message: itemMessage,
|
message: itemMessage,
|
||||||
hasNext: true,
|
hasNext: true,
|
||||||
|
insets: component.insets,
|
||||||
action: {
|
action: {
|
||||||
if let url = webPage?.content.url {
|
if let url = webPage?.content.url {
|
||||||
navigateTo(url)
|
navigateTo(url)
|
||||||
@ -393,6 +401,7 @@ final class BrowserAddressListComponent: Component {
|
|||||||
webPage: TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "https://telegram.org", displayUrl: "https://telegram.org", hash: 0, type: nil, websiteName: "Telegram", title: "Telegram Telegram", text: "Telegram", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil))),
|
webPage: TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "https://telegram.org", displayUrl: "https://telegram.org", hash: 0, type: nil, websiteName: "Telegram", title: "Telegram Telegram", text: "Telegram", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil))),
|
||||||
message: nil,
|
message: nil,
|
||||||
hasNext: true,
|
hasNext: true,
|
||||||
|
insets: .zero,
|
||||||
action: {}
|
action: {}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
|
|||||||
@ -16,6 +16,7 @@ final class BrowserAddressListItemComponent: Component {
|
|||||||
let webPage: TelegramMediaWebpage
|
let webPage: TelegramMediaWebpage
|
||||||
var message: Message?
|
var message: Message?
|
||||||
let hasNext: Bool
|
let hasNext: Bool
|
||||||
|
let insets: UIEdgeInsets
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -24,6 +25,7 @@ final class BrowserAddressListItemComponent: Component {
|
|||||||
webPage: TelegramMediaWebpage,
|
webPage: TelegramMediaWebpage,
|
||||||
message: Message?,
|
message: Message?,
|
||||||
hasNext: Bool,
|
hasNext: Bool,
|
||||||
|
insets: UIEdgeInsets,
|
||||||
action: @escaping () -> Void
|
action: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -31,6 +33,7 @@ final class BrowserAddressListItemComponent: Component {
|
|||||||
self.webPage = webPage
|
self.webPage = webPage
|
||||||
self.message = message
|
self.message = message
|
||||||
self.hasNext = hasNext
|
self.hasNext = hasNext
|
||||||
|
self.insets = insets
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +47,9 @@ final class BrowserAddressListItemComponent: Component {
|
|||||||
if lhs.hasNext != rhs.hasNext {
|
if lhs.hasNext != rhs.hasNext {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.insets != rhs.insets {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +98,7 @@ final class BrowserAddressListItemComponent: Component {
|
|||||||
|
|
||||||
let iconSize = CGSize(width: 40.0, height: 40.0)
|
let iconSize = CGSize(width: 40.0, height: 40.0)
|
||||||
let height: CGFloat = 60.0
|
let height: CGFloat = 60.0
|
||||||
let leftInset: CGFloat = 11.0 + iconSize.width + 11.0
|
let leftInset: CGFloat = component.insets.left + 11.0 + iconSize.width + 11.0
|
||||||
let rightInset: CGFloat = 16.0
|
let rightInset: CGFloat = 16.0
|
||||||
let titleSpacing: CGFloat = 2.0
|
let titleSpacing: CGFloat = 2.0
|
||||||
|
|
||||||
@ -181,7 +187,7 @@ final class BrowserAddressListItemComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let iconFrame = CGRect(origin: CGPoint(x: 11.0, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)
|
let iconFrame = CGRect(origin: CGPoint(x: 11.0 + component.insets.left, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)
|
||||||
|
|
||||||
let iconImageLayout = self.icon.asyncLayout()
|
let iconImageLayout = self.icon.asyncLayout()
|
||||||
var iconImageApply: (() -> Void)?
|
var iconImageApply: (() -> Void)?
|
||||||
|
|||||||
@ -162,12 +162,14 @@ protocol BrowserContent: UIView {
|
|||||||
var present: (ViewController, Any?) -> Void { get set }
|
var present: (ViewController, Any?) -> Void { get set }
|
||||||
var presentInGlobalOverlay: (ViewController) -> Void { get set }
|
var presentInGlobalOverlay: (ViewController) -> Void { get set }
|
||||||
var getNavigationController: () -> NavigationController? { get set }
|
var getNavigationController: () -> NavigationController? { get set }
|
||||||
|
var openAppUrl: (String) -> Void { get set }
|
||||||
|
|
||||||
var minimize: () -> Void { get set }
|
var minimize: () -> Void { get set }
|
||||||
var close: () -> Void { get set }
|
var close: () -> Void { get set }
|
||||||
|
|
||||||
var onScrollingUpdate: (ContentScrollingUpdate) -> Void { get set }
|
var onScrollingUpdate: (ContentScrollingUpdate) -> Void { get set }
|
||||||
|
func resetScrolling()
|
||||||
|
|
||||||
func reload()
|
func reload()
|
||||||
func stop()
|
func stop()
|
||||||
|
|
||||||
@ -186,7 +188,7 @@ protocol BrowserContent: UIView {
|
|||||||
|
|
||||||
func addToRecentlyVisited()
|
func addToRecentlyVisited()
|
||||||
|
|
||||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition)
|
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition)
|
||||||
|
|
||||||
func makeContentSnapshotView() -> UIView?
|
func makeContentSnapshotView() -> UIView?
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import ShareController
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
|
|
||||||
|
|
||||||
final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate {
|
final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
@ -37,6 +36,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
||||||
|
var openAppUrl: (String) -> Void = { _ in }
|
||||||
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
||||||
var minimize: () -> Void = { }
|
var minimize: () -> Void = { }
|
||||||
var close: () -> Void = { }
|
var close: () -> Void = { }
|
||||||
@ -101,7 +101,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
|
|||||||
self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor
|
self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor
|
||||||
}
|
}
|
||||||
if let (size, insets, fullInsets) = self.validLayout {
|
if let (size, insets, fullInsets) = self.validLayout {
|
||||||
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: .immediate)
|
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +240,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets)?
|
private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets)?
|
||||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition) {
|
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) {
|
||||||
self.validLayout = (size, insets, fullInsets)
|
self.validLayout = (size, insets, fullInsets)
|
||||||
|
|
||||||
self.previousScrollingOffset = ScrollingOffsetState(value: self.webView.scrollView.contentOffset.y, isDraggingOrDecelerating: self.webView.scrollView.isDragging || self.webView.scrollView.isDecelerating)
|
self.previousScrollingOffset = ScrollingOffsetState(value: self.webView.scrollView.contentOffset.y, isDraggingOrDecelerating: self.webView.scrollView.isDragging || self.webView.scrollView.isDecelerating)
|
||||||
@ -360,6 +360,10 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetScrolling() {
|
||||||
|
self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
|
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
|
||||||
// self.currentError = nil
|
// self.currentError = nil
|
||||||
self.updateFontState(self.currentFontState, force: true)
|
self.updateFontState(self.currentFontState, force: true)
|
||||||
|
|||||||
@ -66,6 +66,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
var currentAccessibilityAreas: [AccessibilityAreaNode] = []
|
var currentAccessibilityAreas: [AccessibilityAreaNode] = []
|
||||||
|
|
||||||
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
||||||
|
var openAppUrl: (String) -> Void = { _ in }
|
||||||
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
||||||
var minimize: () -> Void = { }
|
var minimize: () -> Void = { }
|
||||||
var close: () -> Void = { }
|
var close: () -> Void = { }
|
||||||
@ -300,7 +301,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
guard let (size, insets, fullInsets) = self.containerLayout else {
|
guard let (size, insets, fullInsets) = self.containerLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: transition)
|
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reload() {
|
func reload() {
|
||||||
@ -374,11 +375,11 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
|
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition) {
|
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) {
|
||||||
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: transition.containedViewLayoutTransition)
|
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: safeInsets, transition: transition.containedViewLayoutTransition)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
|
||||||
self.containerLayout = (size, insets, fullInsets)
|
self.containerLayout = (size, insets, fullInsets)
|
||||||
|
|
||||||
var updateVisibleItems = false
|
var updateVisibleItems = false
|
||||||
@ -766,6 +767,10 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
self.readingProgress.set(readingProgress)
|
self.readingProgress.set(readingProgress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetScrolling() {
|
||||||
|
self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
|
||||||
private func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint {
|
private func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint {
|
||||||
var contentOffset = CGPoint()
|
var contentOffset = CGPoint()
|
||||||
for (_, itemNode) in self.visibleItemsWithNodes {
|
for (_, itemNode) in self.visibleItemsWithNodes {
|
||||||
|
|||||||
@ -29,12 +29,14 @@ final class BrowserNavigationBarComponent: CombinedComponent {
|
|||||||
let topInset: CGFloat
|
let topInset: CGFloat
|
||||||
let height: CGFloat
|
let height: CGFloat
|
||||||
let sideInset: CGFloat
|
let sideInset: CGFloat
|
||||||
|
let metrics: LayoutMetrics
|
||||||
let leftItems: [AnyComponentWithIdentity<Empty>]
|
let leftItems: [AnyComponentWithIdentity<Empty>]
|
||||||
let rightItems: [AnyComponentWithIdentity<Empty>]
|
let rightItems: [AnyComponentWithIdentity<Empty>]
|
||||||
let centerItem: AnyComponentWithIdentity<BrowserNavigationBarEnvironment>?
|
let centerItem: AnyComponentWithIdentity<BrowserNavigationBarEnvironment>?
|
||||||
let readingProgress: CGFloat
|
let readingProgress: CGFloat
|
||||||
let loadingProgress: Double?
|
let loadingProgress: Double?
|
||||||
let collapseFraction: CGFloat
|
let collapseFraction: CGFloat
|
||||||
|
let activate: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
backgroundColor: UIColor,
|
backgroundColor: UIColor,
|
||||||
@ -45,12 +47,14 @@ final class BrowserNavigationBarComponent: CombinedComponent {
|
|||||||
topInset: CGFloat,
|
topInset: CGFloat,
|
||||||
height: CGFloat,
|
height: CGFloat,
|
||||||
sideInset: CGFloat,
|
sideInset: CGFloat,
|
||||||
|
metrics: LayoutMetrics,
|
||||||
leftItems: [AnyComponentWithIdentity<Empty>],
|
leftItems: [AnyComponentWithIdentity<Empty>],
|
||||||
rightItems: [AnyComponentWithIdentity<Empty>],
|
rightItems: [AnyComponentWithIdentity<Empty>],
|
||||||
centerItem: AnyComponentWithIdentity<BrowserNavigationBarEnvironment>?,
|
centerItem: AnyComponentWithIdentity<BrowserNavigationBarEnvironment>?,
|
||||||
readingProgress: CGFloat,
|
readingProgress: CGFloat,
|
||||||
loadingProgress: Double?,
|
loadingProgress: Double?,
|
||||||
collapseFraction: CGFloat
|
collapseFraction: CGFloat,
|
||||||
|
activate: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.separatorColor = separatorColor
|
self.separatorColor = separatorColor
|
||||||
@ -60,12 +64,14 @@ final class BrowserNavigationBarComponent: CombinedComponent {
|
|||||||
self.topInset = topInset
|
self.topInset = topInset
|
||||||
self.height = height
|
self.height = height
|
||||||
self.sideInset = sideInset
|
self.sideInset = sideInset
|
||||||
|
self.metrics = metrics
|
||||||
self.leftItems = leftItems
|
self.leftItems = leftItems
|
||||||
self.rightItems = rightItems
|
self.rightItems = rightItems
|
||||||
self.centerItem = centerItem
|
self.centerItem = centerItem
|
||||||
self.readingProgress = readingProgress
|
self.readingProgress = readingProgress
|
||||||
self.loadingProgress = loadingProgress
|
self.loadingProgress = loadingProgress
|
||||||
self.collapseFraction = collapseFraction
|
self.collapseFraction = collapseFraction
|
||||||
|
self.activate = activate
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: BrowserNavigationBarComponent, rhs: BrowserNavigationBarComponent) -> Bool {
|
static func ==(lhs: BrowserNavigationBarComponent, rhs: BrowserNavigationBarComponent) -> Bool {
|
||||||
@ -93,6 +99,9 @@ final class BrowserNavigationBarComponent: CombinedComponent {
|
|||||||
if lhs.sideInset != rhs.sideInset {
|
if lhs.sideInset != rhs.sideInset {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.metrics != rhs.metrics {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.leftItems != rhs.leftItems {
|
if lhs.leftItems != rhs.leftItems {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -122,6 +131,7 @@ final class BrowserNavigationBarComponent: CombinedComponent {
|
|||||||
let leftItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
|
let leftItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
|
||||||
let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
|
let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
|
||||||
let centerItems = ChildMap(environment: BrowserNavigationBarEnvironment.self, keyedBy: AnyHashable.self)
|
let centerItems = ChildMap(environment: BrowserNavigationBarEnvironment.self, keyedBy: AnyHashable.self)
|
||||||
|
let activate = Child(Button.self)
|
||||||
|
|
||||||
return { context in
|
return { context in
|
||||||
var availableWidth = context.availableSize.width
|
var availableWidth = context.availableSize.width
|
||||||
@ -131,6 +141,8 @@ final class BrowserNavigationBarComponent: CombinedComponent {
|
|||||||
let expandedHeight = context.component.height
|
let expandedHeight = context.component.height
|
||||||
let contentHeight: CGFloat = expandedHeight * (1.0 - context.component.collapseFraction) + collapsedHeight * context.component.collapseFraction
|
let contentHeight: CGFloat = expandedHeight * (1.0 - context.component.collapseFraction) + collapsedHeight * context.component.collapseFraction
|
||||||
let size = CGSize(width: context.availableSize.width, height: context.component.topInset + contentHeight)
|
let size = CGSize(width: context.availableSize.width, height: context.component.topInset + contentHeight)
|
||||||
|
let verticalOffset: CGFloat = context.component.metrics.isTablet ? -3.0 : 0.0
|
||||||
|
let itemSpacing: CGFloat = context.component.metrics.isTablet ? 24.0 : 8.0
|
||||||
|
|
||||||
let background = background.update(
|
let background = background.update(
|
||||||
component: Rectangle(color: context.component.backgroundColor.withAlphaComponent(1.0)),
|
component: Rectangle(color: context.component.backgroundColor.withAlphaComponent(1.0)),
|
||||||
@ -160,7 +172,7 @@ final class BrowserNavigationBarComponent: CombinedComponent {
|
|||||||
availableSize: CGSize(width: size.width, height: size.height),
|
availableSize: CGSize(width: size.width, height: size.height),
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
|
|
||||||
var leftItemList: [_UpdatedChildComponent] = []
|
var leftItemList: [_UpdatedChildComponent] = []
|
||||||
for item in context.component.leftItems {
|
for item in context.component.leftItems {
|
||||||
let item = leftItems[item.id].update(
|
let item = leftItems[item.id].update(
|
||||||
@ -182,11 +194,7 @@ final class BrowserNavigationBarComponent: CombinedComponent {
|
|||||||
rightItemList.append(item)
|
rightItemList.append(item)
|
||||||
availableWidth -= item.size.width
|
availableWidth -= item.size.width
|
||||||
}
|
}
|
||||||
|
|
||||||
if !leftItemList.isEmpty || !rightItemList.isEmpty {
|
|
||||||
availableWidth -= 14.0
|
|
||||||
}
|
|
||||||
|
|
||||||
context.add(background
|
context.add(background
|
||||||
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||||
)
|
)
|
||||||
@ -212,35 +220,37 @@ final class BrowserNavigationBarComponent: CombinedComponent {
|
|||||||
var leftItemX = sideInset
|
var leftItemX = sideInset
|
||||||
for item in leftItemList {
|
for item in leftItemList {
|
||||||
context.add(item
|
context.add(item
|
||||||
.position(CGPoint(x: leftItemX + item.size.width / 2.0 - (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0))
|
.position(CGPoint(x: leftItemX + item.size.width / 2.0 - (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0 + verticalOffset))
|
||||||
.scale(1.0 - 0.35 * context.component.collapseFraction)
|
.scale(1.0 - 0.35 * context.component.collapseFraction)
|
||||||
.opacity(1.0 - context.component.collapseFraction)
|
.opacity(1.0 - context.component.collapseFraction)
|
||||||
.appear(.default(scale: true, alpha: true))
|
.appear(.default(scale: true, alpha: true))
|
||||||
.disappear(.default(scale: true, alpha: true))
|
.disappear(.default(scale: true, alpha: true))
|
||||||
)
|
)
|
||||||
leftItemX += item.size.width + 8.0
|
leftItemX += item.size.width + itemSpacing
|
||||||
centerLeftInset += item.size.width + 8.0
|
centerLeftInset += item.size.width + itemSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
var centerRightInset = sideInset - 5.0
|
var centerRightInset = sideInset - 5.0
|
||||||
var rightItemX = context.availableSize.width - (sideInset - 5.0)
|
var rightItemX = context.availableSize.width - (sideInset - 5.0)
|
||||||
for item in rightItemList.reversed() {
|
for item in rightItemList.reversed() {
|
||||||
context.add(item
|
context.add(item
|
||||||
.position(CGPoint(x: rightItemX - item.size.width / 2.0 + (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0))
|
.position(CGPoint(x: rightItemX - item.size.width / 2.0 + (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0 + verticalOffset))
|
||||||
.scale(1.0 - 0.35 * context.component.collapseFraction)
|
.scale(1.0 - 0.35 * context.component.collapseFraction)
|
||||||
.opacity(1.0 - context.component.collapseFraction)
|
.opacity(1.0 - context.component.collapseFraction)
|
||||||
.appear(.default(scale: true, alpha: true))
|
.appear(.default(scale: true, alpha: true))
|
||||||
.disappear(.default(scale: true, alpha: true))
|
.disappear(.default(scale: true, alpha: true))
|
||||||
)
|
)
|
||||||
rightItemX -= item.size.width + 8.0
|
rightItemX -= item.size.width + itemSpacing
|
||||||
centerRightInset += item.size.width + 8.0
|
centerRightInset += item.size.width + itemSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
let maxCenterInset = max(centerLeftInset, centerRightInset)
|
let maxCenterInset = max(centerLeftInset, centerRightInset)
|
||||||
|
|
||||||
if !leftItemList.isEmpty || !rightItemList.isEmpty {
|
if !leftItemList.isEmpty || !rightItemList.isEmpty {
|
||||||
availableWidth -= 20.0
|
availableWidth -= itemSpacing * CGFloat(max(0, leftItemList.count - 1)) + itemSpacing * CGFloat(max(0, rightItemList.count - 1)) + 30.0
|
||||||
}
|
}
|
||||||
|
availableWidth -= context.component.sideInset * 2.0
|
||||||
|
availableWidth = min(660.0, availableWidth)
|
||||||
|
|
||||||
let environment = BrowserNavigationBarEnvironment(fraction: context.component.collapseFraction)
|
let environment = BrowserNavigationBarEnvironment(fraction: context.component.collapseFraction)
|
||||||
|
|
||||||
@ -259,13 +269,30 @@ final class BrowserNavigationBarComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
if let centerItem = centerItem {
|
if let centerItem = centerItem {
|
||||||
context.add(centerItem
|
context.add(centerItem
|
||||||
.position(CGPoint(x: centerX, y: context.component.topInset + contentHeight / 2.0))
|
.position(CGPoint(x: centerX, y: context.component.topInset + contentHeight / 2.0 + verticalOffset))
|
||||||
.scale(1.0 - 0.35 * context.component.collapseFraction)
|
.scale(1.0 - 0.35 * context.component.collapseFraction)
|
||||||
.appear(.default(scale: false, alpha: true))
|
.appear(.default(scale: false, alpha: true))
|
||||||
.disappear(.default(scale: false, alpha: true))
|
.disappear(.default(scale: false, alpha: true))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.component.collapseFraction == 1.0 {
|
||||||
|
let activateAction = context.component.activate
|
||||||
|
let activate = activate.update(
|
||||||
|
component: Button(
|
||||||
|
content: AnyComponent(Rectangle(color: UIColor(rgb: 0x000000, alpha: 0.001))),
|
||||||
|
action: {
|
||||||
|
activateAction()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
availableSize: size,
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
context.add(activate
|
||||||
|
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,6 +38,7 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
||||||
|
var openAppUrl: (String) -> Void = { _ in }
|
||||||
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
||||||
var minimize: () -> Void = { }
|
var minimize: () -> Void = { }
|
||||||
var close: () -> Void = { }
|
var close: () -> Void = { }
|
||||||
@ -110,7 +111,7 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
self.backgroundColor = presentationData.theme.list.plainBackgroundColor
|
self.backgroundColor = presentationData.theme.list.plainBackgroundColor
|
||||||
}
|
}
|
||||||
if let (size, insets, fullInsets) = self.validLayout {
|
if let (size, insets, fullInsets) = self.validLayout {
|
||||||
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: .immediate)
|
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +250,7 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets)?
|
private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets)?
|
||||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition) {
|
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) {
|
||||||
self.validLayout = (size, insets, fullInsets)
|
self.validLayout = (size, insets, fullInsets)
|
||||||
|
|
||||||
self.previousScrollingOffset = ScrollingOffsetState(value: self.scrollView.contentOffset.y, isDraggingOrDecelerating: self.scrollView.isDragging || self.scrollView.isDecelerating)
|
self.previousScrollingOffset = ScrollingOffsetState(value: self.scrollView.contentOffset.y, isDraggingOrDecelerating: self.scrollView.isDragging || self.scrollView.isDecelerating)
|
||||||
@ -352,6 +353,10 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetScrolling() {
|
||||||
|
self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
|
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
|
||||||
// self.currentError = nil
|
// self.currentError = nil
|
||||||
self.updateFontState(self.currentFontState, force: true)
|
self.updateFontState(self.currentFontState, force: true)
|
||||||
|
|||||||
@ -80,6 +80,8 @@ private final class BrowserScreenComponent: CombinedComponent {
|
|||||||
let performAction = context.component.performAction
|
let performAction = context.component.performAction
|
||||||
let performHoldAction = context.component.performHoldAction
|
let performHoldAction = context.component.performHoldAction
|
||||||
|
|
||||||
|
let isTablet = environment.metrics.isTablet
|
||||||
|
|
||||||
let navigationContent: AnyComponentWithIdentity<BrowserNavigationBarEnvironment>?
|
let navigationContent: AnyComponentWithIdentity<BrowserNavigationBarEnvironment>?
|
||||||
var navigationLeftItems: [AnyComponentWithIdentity<Empty>]
|
var navigationLeftItems: [AnyComponentWithIdentity<Empty>]
|
||||||
var navigationRightItems: [AnyComponentWithIdentity<Empty>]
|
var navigationRightItems: [AnyComponentWithIdentity<Empty>]
|
||||||
@ -106,6 +108,7 @@ private final class BrowserScreenComponent: CombinedComponent {
|
|||||||
AddressBarContentComponent(
|
AddressBarContentComponent(
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
|
metrics: environment.metrics,
|
||||||
url: context.component.contentState?.url ?? "",
|
url: context.component.contentState?.url ?? "",
|
||||||
isSecure: context.component.contentState?.isSecure ?? false,
|
isSecure: context.component.contentState?.isSecure ?? false,
|
||||||
isExpanded: context.component.presentationState.addressFocused,
|
isExpanded: context.component.presentationState.addressFocused,
|
||||||
@ -126,7 +129,7 @@ private final class BrowserScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.component.presentationState.addressFocused {
|
if context.component.presentationState.addressFocused && !isTablet {
|
||||||
navigationLeftItems = []
|
navigationLeftItems = []
|
||||||
navigationRightItems = []
|
navigationRightItems = []
|
||||||
} else {
|
} else {
|
||||||
@ -146,6 +149,68 @@ private final class BrowserScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if isTablet {
|
||||||
|
navigationLeftItems.append(
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: "minimize",
|
||||||
|
component: AnyComponent(
|
||||||
|
Button(
|
||||||
|
content: AnyComponent(
|
||||||
|
BundleIconComponent(
|
||||||
|
name: "Media Gallery/PictureInPictureButton",
|
||||||
|
tintColor: environment.theme.rootController.navigationBar.accentTextColor
|
||||||
|
)
|
||||||
|
),
|
||||||
|
action: {
|
||||||
|
performAction.invoke(.close)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let canGoBack = context.component.contentState?.canGoBack ?? false
|
||||||
|
let canGoForward = context.component.contentState?.canGoForward ?? false
|
||||||
|
|
||||||
|
navigationLeftItems.append(
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: "back",
|
||||||
|
component: AnyComponent(
|
||||||
|
Button(
|
||||||
|
content: AnyComponent(
|
||||||
|
BundleIconComponent(
|
||||||
|
name: "Instant View/Back",
|
||||||
|
tintColor: environment.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(canGoBack ? 1.0 : 0.4)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
action: {
|
||||||
|
performAction.invoke(.navigateBack)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
navigationLeftItems.append(
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: "forward",
|
||||||
|
component: AnyComponent(
|
||||||
|
Button(
|
||||||
|
content: AnyComponent(
|
||||||
|
BundleIconComponent(
|
||||||
|
name: "Instant View/Forward",
|
||||||
|
tintColor: environment.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(canGoForward ? 1.0 : 0.4)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
action: {
|
||||||
|
performAction.invoke(.navigateForward)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
navigationRightItems = [
|
navigationRightItems = [
|
||||||
AnyComponentWithIdentity(
|
AnyComponentWithIdentity(
|
||||||
id: "settings",
|
id: "settings",
|
||||||
@ -168,6 +233,65 @@ private final class BrowserScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if isTablet {
|
||||||
|
navigationRightItems.insert(
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: "bookmarks",
|
||||||
|
component: AnyComponent(
|
||||||
|
Button(
|
||||||
|
content: AnyComponent(
|
||||||
|
BundleIconComponent(
|
||||||
|
name: "Instant View/Bookmark",
|
||||||
|
tintColor: environment.theme.rootController.navigationBar.accentTextColor
|
||||||
|
)
|
||||||
|
),
|
||||||
|
action: {
|
||||||
|
performAction.invoke(.openBookmarks)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
at: 0
|
||||||
|
)
|
||||||
|
navigationRightItems.insert(
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: "share",
|
||||||
|
component: AnyComponent(
|
||||||
|
Button(
|
||||||
|
content: AnyComponent(
|
||||||
|
BundleIconComponent(
|
||||||
|
name: "Chat List/NavigationShare",
|
||||||
|
tintColor: environment.theme.rootController.navigationBar.accentTextColor
|
||||||
|
)
|
||||||
|
),
|
||||||
|
action: {
|
||||||
|
performAction.invoke(.share)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
at: 0
|
||||||
|
)
|
||||||
|
navigationRightItems.append(
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: "openIn",
|
||||||
|
component: AnyComponent(
|
||||||
|
Button(
|
||||||
|
content: AnyComponent(
|
||||||
|
BundleIconComponent(
|
||||||
|
name: "Instant View/Browser",
|
||||||
|
tintColor: environment.theme.rootController.navigationBar.accentTextColor
|
||||||
|
)
|
||||||
|
),
|
||||||
|
action: {
|
||||||
|
performAction.invoke(.openIn)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,12 +307,16 @@ private final class BrowserScreenComponent: CombinedComponent {
|
|||||||
topInset: environment.statusBarHeight,
|
topInset: environment.statusBarHeight,
|
||||||
height: environment.navigationHeight - environment.statusBarHeight,
|
height: environment.navigationHeight - environment.statusBarHeight,
|
||||||
sideInset: environment.safeInsets.left,
|
sideInset: environment.safeInsets.left,
|
||||||
|
metrics: environment.metrics,
|
||||||
leftItems: navigationLeftItems,
|
leftItems: navigationLeftItems,
|
||||||
rightItems: navigationRightItems,
|
rightItems: navigationRightItems,
|
||||||
centerItem: navigationContent,
|
centerItem: navigationContent,
|
||||||
readingProgress: context.component.contentState?.readingProgress ?? 0.0,
|
readingProgress: context.component.contentState?.readingProgress ?? 0.0,
|
||||||
loadingProgress: context.component.contentState?.estimatedProgress,
|
loadingProgress: context.component.contentState?.estimatedProgress,
|
||||||
collapseFraction: collapseFraction
|
collapseFraction: collapseFraction,
|
||||||
|
activate: {
|
||||||
|
performAction.invoke(.expand)
|
||||||
|
}
|
||||||
),
|
),
|
||||||
availableSize: context.availableSize,
|
availableSize: context.availableSize,
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
@ -235,22 +363,36 @@ private final class BrowserScreenComponent: CombinedComponent {
|
|||||||
toolbarBottomInset = environment.safeInsets.bottom
|
toolbarBottomInset = environment.safeInsets.bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
let toolbar = toolbar.update(
|
var toolbarSize: CGFloat = 0.0
|
||||||
component: BrowserToolbarComponent(
|
if isTablet && !context.component.presentationState.isSearching {
|
||||||
backgroundColor: environment.theme.rootController.navigationBar.blurredBackgroundColor,
|
|
||||||
separatorColor: environment.theme.rootController.navigationBar.separatorColor,
|
} else {
|
||||||
textColor: environment.theme.rootController.navigationBar.primaryTextColor,
|
let toolbar = toolbar.update(
|
||||||
bottomInset: toolbarBottomInset,
|
component: BrowserToolbarComponent(
|
||||||
sideInset: environment.safeInsets.left,
|
backgroundColor: environment.theme.rootController.navigationBar.blurredBackgroundColor,
|
||||||
item: toolbarContent,
|
separatorColor: environment.theme.rootController.navigationBar.separatorColor,
|
||||||
collapseFraction: collapseFraction
|
textColor: environment.theme.rootController.navigationBar.primaryTextColor,
|
||||||
),
|
bottomInset: toolbarBottomInset,
|
||||||
availableSize: context.availableSize,
|
sideInset: environment.safeInsets.left,
|
||||||
transition: context.transition
|
item: toolbarContent,
|
||||||
)
|
collapseFraction: collapseFraction
|
||||||
context.add(toolbar
|
),
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0))
|
availableSize: context.availableSize,
|
||||||
)
|
transition: context.transition
|
||||||
|
)
|
||||||
|
context.add(toolbar
|
||||||
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0))
|
||||||
|
.appear(ComponentTransition.Appear { _, view, transition in
|
||||||
|
transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: view.frame.height), to: CGPoint(), additive: true)
|
||||||
|
})
|
||||||
|
.disappear(ComponentTransition.Disappear { view, transition, completion in
|
||||||
|
transition.animatePosition(view: view, from: CGPoint(), to: CGPoint(x: 0.0, y: view.frame.height), additive: true, completion: { _ in
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
toolbarSize = toolbar.size.height
|
||||||
|
}
|
||||||
|
|
||||||
if context.component.presentationState.addressFocused {
|
if context.component.presentationState.addressFocused {
|
||||||
let addressList = addressList.update(
|
let addressList = addressList.update(
|
||||||
@ -258,15 +400,17 @@ private final class BrowserScreenComponent: CombinedComponent {
|
|||||||
context: context.component.context,
|
context: context.component.context,
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
|
insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right),
|
||||||
navigateTo: { url in
|
navigateTo: { url in
|
||||||
performAction.invoke(.navigateTo(url))
|
performAction.invoke(.navigateTo(url))
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height - navigationBar.size.height - toolbar.size.height),
|
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height - navigationBar.size.height - toolbarSize),
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
context.add(addressList
|
context.add(addressList
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height + addressList.size.height / 2.0))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height + addressList.size.height / 2.0))
|
||||||
|
.clipsToBounds(true)
|
||||||
.appear(.default(alpha: true))
|
.appear(.default(alpha: true))
|
||||||
.disappear(.default(alpha: true))
|
.disappear(.default(alpha: true))
|
||||||
)
|
)
|
||||||
@ -314,6 +458,7 @@ public class BrowserScreen: ViewController, MinimizableController {
|
|||||||
case openAddressBar
|
case openAddressBar
|
||||||
case closeAddressBar
|
case closeAddressBar
|
||||||
case navigateTo(String)
|
case navigateTo(String)
|
||||||
|
case expand
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate final class Node: ViewControllerTracingNode {
|
fileprivate final class Node: ViewControllerTracingNode {
|
||||||
@ -568,6 +713,10 @@ public class BrowserScreen: ViewController, MinimizableController {
|
|||||||
updatedState.addressFocused = false
|
updatedState.addressFocused = false
|
||||||
return updatedState
|
return updatedState
|
||||||
})
|
})
|
||||||
|
case .expand:
|
||||||
|
if let content = self.content.last {
|
||||||
|
content.resetScrolling()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,6 +775,14 @@ public class BrowserScreen: ViewController, MinimizableController {
|
|||||||
}
|
}
|
||||||
self.pushContent(content, transition: .spring(duration: 0.4))
|
self.pushContent(content, transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
|
browserContent.openAppUrl = { [weak self] url in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: false, presentationData: self.presentationData, navigationController: self.controller?.navigationController as? NavigationController, dismissInput: { [weak self] in
|
||||||
|
self?.view.window?.endEditing(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
browserContent.present = { [weak self] c, a in
|
browserContent.present = { [weak self] c, a in
|
||||||
guard let self, let controller = self.controller else {
|
guard let self, let controller = self.controller else {
|
||||||
return
|
return
|
||||||
@ -989,6 +1146,10 @@ public class BrowserScreen: ViewController, MinimizableController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if update.isReset {
|
||||||
|
scrollingPanelOffsetFraction = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
if scrollingPanelOffsetFraction != self.scrollingPanelOffsetFraction {
|
if scrollingPanelOffsetFraction != self.scrollingPanelOffsetFraction {
|
||||||
self.scrollingPanelOffsetFraction = scrollingPanelOffsetFraction
|
self.scrollingPanelOffsetFraction = scrollingPanelOffsetFraction
|
||||||
self.requestLayout(transition: transition)
|
self.requestLayout(transition: transition)
|
||||||
@ -1168,7 +1329,7 @@ public class BrowserScreen: ViewController, MinimizableController {
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let subject: Subject
|
private let subject: Subject
|
||||||
|
|
||||||
var openPreviousOnClose = false
|
private var openPreviousOnClose = false
|
||||||
|
|
||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: ContainerViewLayout?
|
||||||
|
|
||||||
@ -1184,9 +1345,10 @@ public class BrowserScreen: ViewController, MinimizableController {
|
|||||||
// "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
// "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
||||||
]
|
]
|
||||||
|
|
||||||
public init(context: AccountContext, subject: Subject) {
|
public init(context: AccountContext, subject: Subject, openPreviousOnClose: Bool = false) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
|
self.openPreviousOnClose = openPreviousOnClose
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
@ -1218,13 +1380,35 @@ public class BrowserScreen: ViewController, MinimizableController {
|
|||||||
|
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
self.node.containerLayoutUpdated(layout: layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.height, transition: ComponentTransition(transition))
|
var navigationHeight = self.navigationLayout(layout: layout).navigationFrame.height
|
||||||
|
if layout.metrics.isTablet, layout.size.width > layout.size.height {
|
||||||
|
navigationHeight += 6.0
|
||||||
|
}
|
||||||
|
self.node.containerLayoutUpdated(layout: layout, navigationBarHeight: navigationHeight, transition: ComponentTransition(transition))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestMinimize(topEdgeOffset: CGFloat?, initialVelocity: CGFloat?) {
|
public func requestMinimize(topEdgeOffset: CGFloat?, initialVelocity: CGFloat?) {
|
||||||
|
self.openPreviousOnClose = false
|
||||||
self.node.minimize(topEdgeOffset: topEdgeOffset, damping: 180.0, initialVelocity: initialVelocity)
|
self.node.minimize(topEdgeOffset: topEdgeOffset, damping: 180.0, initialVelocity: initialVelocity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var didPlayAppearanceAnimation = false
|
||||||
|
public override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
if !self.didPlayAppearanceAnimation, let layout = self.validLayout, layout.metrics.isTablet {
|
||||||
|
self.node.layer.animatePosition(from: CGPoint(x: 0.0, y: layout.size.height), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
if self.openPreviousOnClose, let navigationController = self.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer, let controller = minimizedContainer.controllers.last {
|
||||||
|
navigationController.maximizeViewController(controller, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var isMinimized = false {
|
public var isMinimized = false {
|
||||||
didSet {
|
didSet {
|
||||||
if let webContent = self.node.content.last as? BrowserWebContent {
|
if let webContent = self.node.content.last as? BrowserWebContent {
|
||||||
@ -1339,8 +1523,8 @@ private final class BrowserContentComponent: Component {
|
|||||||
let bottomInset = (49.0 + component.insets.bottom) * (1.0 - component.scrollingPanelOffsetFraction)
|
let bottomInset = (49.0 + component.insets.bottom) * (1.0 - component.scrollingPanelOffsetFraction)
|
||||||
let insets = UIEdgeInsets(top: topInset, left: component.insets.left, bottom: bottomInset, right: component.insets.right)
|
let insets = UIEdgeInsets(top: topInset, left: component.insets.left, bottom: bottomInset, right: component.insets.right)
|
||||||
let fullInsets = UIEdgeInsets(top: component.insets.top + component.navigationBarHeight, left: component.insets.left, bottom: 49.0 + component.insets.bottom, right: component.insets.right)
|
let fullInsets = UIEdgeInsets(top: component.insets.top + component.navigationBarHeight, left: component.insets.left, bottom: 49.0 + component.insets.bottom, right: component.insets.right)
|
||||||
|
|
||||||
component.content.updateLayout(size: availableSize, insets: insets, fullInsets: fullInsets, transition: transition)
|
component.content.updateLayout(size: availableSize, insets: insets, fullInsets: fullInsets, safeInsets: component.insets, transition: transition)
|
||||||
transition.setFrame(view: component.content, frame: CGRect(origin: .zero, size: availableSize))
|
transition.setFrame(view: component.content, frame: CGRect(origin: .zero, size: availableSize))
|
||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
|
|||||||
@ -18,6 +18,8 @@ import UndoUI
|
|||||||
import LottieComponent
|
import LottieComponent
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
|
import UrlHandling
|
||||||
|
import SaveProgressScreen
|
||||||
|
|
||||||
private final class TonSchemeHandler: NSObject, WKURLSchemeHandler {
|
private final class TonSchemeHandler: NSObject, WKURLSchemeHandler {
|
||||||
private final class PendingTask {
|
private final class PendingTask {
|
||||||
@ -115,11 +117,23 @@ private final class TonSchemeHandler: NSObject, WKURLSchemeHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class WebView: WKWebView {
|
||||||
|
var customBottomInset: CGFloat = 0.0 {
|
||||||
|
didSet {
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var safeAreaInsets: UIEdgeInsets {
|
||||||
|
return UIEdgeInsets(top: 0.0, left: 0.0, bottom: self.customBottomInset, right: 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate {
|
final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
|
||||||
let webView: WKWebView
|
let webView: WebView
|
||||||
|
|
||||||
private let errorView: ComponentHostView<Empty>
|
private let errorView: ComponentHostView<Empty>
|
||||||
private var currentError: Error?
|
private var currentError: Error?
|
||||||
@ -139,6 +153,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
private let faviconDisposable = MetaDisposable()
|
private let faviconDisposable = MetaDisposable()
|
||||||
|
|
||||||
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
||||||
|
var openAppUrl: (String) -> Void = { _ in }
|
||||||
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
||||||
var minimize: () -> Void = { }
|
var minimize: () -> Void = { }
|
||||||
var close: () -> Void = { }
|
var close: () -> Void = { }
|
||||||
@ -155,23 +170,19 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
|
|
||||||
let configuration = WKWebViewConfiguration()
|
let configuration = WKWebViewConfiguration()
|
||||||
|
|
||||||
// let bundle = Bundle.main
|
|
||||||
// let bundleVersion = bundle.infoDictionary?["CFBundleShortVersionString"] ?? ""
|
|
||||||
//
|
|
||||||
var proxyServerHost = "magic.org"
|
var proxyServerHost = "magic.org"
|
||||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let hostValue = data["ton_proxy_address"] as? String {
|
if let data = context.currentAppConfiguration.with({ $0 }).data, let hostValue = data["ton_proxy_address"] as? String {
|
||||||
proxyServerHost = hostValue
|
proxyServerHost = hostValue
|
||||||
}
|
}
|
||||||
configuration.setURLSchemeHandler(TonSchemeHandler(proxyServerHost: proxyServerHost), forURLScheme: "tonsite")
|
configuration.setURLSchemeHandler(TonSchemeHandler(proxyServerHost: proxyServerHost), forURLScheme: "tonsite")
|
||||||
configuration.allowsInlineMediaPlayback = true
|
configuration.allowsInlineMediaPlayback = true
|
||||||
// configuration.applicationNameForUserAgent = "Telegram-iOS/\(bundleVersion)"
|
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||||
configuration.mediaTypesRequiringUserActionForPlayback = []
|
configuration.mediaTypesRequiringUserActionForPlayback = []
|
||||||
} else {
|
} else {
|
||||||
configuration.mediaPlaybackRequiresUserAction = false
|
configuration.mediaPlaybackRequiresUserAction = false
|
||||||
}
|
}
|
||||||
|
|
||||||
self.webView = WKWebView(frame: CGRect(), configuration: configuration)
|
self.webView = WebView(frame: CGRect(), configuration: configuration)
|
||||||
self.webView.allowsLinkPreview = true
|
self.webView.allowsLinkPreview = true
|
||||||
|
|
||||||
if #available(iOS 11.0, *) {
|
if #available(iOS 11.0, *) {
|
||||||
@ -201,6 +212,9 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
self.backgroundColor = presentationData.theme.list.plainBackgroundColor
|
||||||
|
self.webView.backgroundColor = presentationData.theme.list.plainBackgroundColor
|
||||||
|
|
||||||
self.webView.allowsBackForwardNavigationGestures = true
|
self.webView.allowsBackForwardNavigationGestures = true
|
||||||
self.webView.scrollView.delegate = self
|
self.webView.scrollView.delegate = self
|
||||||
self.webView.scrollView.clipsToBounds = false
|
self.webView.scrollView.clipsToBounds = false
|
||||||
@ -214,7 +228,6 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.canGoForward), options: [], context: nil)
|
self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.canGoForward), options: [], context: nil)
|
||||||
self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.hasOnlySecureContent), options: [], context: nil)
|
self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.hasOnlySecureContent), options: [], context: nil)
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
self.backgroundColor = presentationData.theme.list.plainBackgroundColor
|
|
||||||
self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor
|
self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor
|
||||||
}
|
}
|
||||||
if #available(iOS 16.4, *) {
|
if #available(iOS 16.4, *) {
|
||||||
@ -244,8 +257,8 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
self.backgroundColor = presentationData.theme.list.plainBackgroundColor
|
self.backgroundColor = presentationData.theme.list.plainBackgroundColor
|
||||||
self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor
|
self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor
|
||||||
}
|
}
|
||||||
if let (size, insets, fullInsets) = self.validLayout {
|
if let (size, insets, fullInsets, safeInsets) = self.validLayout {
|
||||||
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: .immediate)
|
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: safeInsets, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,13 +446,13 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
self.webView.scrollView.setContentOffset(CGPoint(x: 0.0, y: -self.webView.scrollView.contentInset.top), animated: true)
|
self.webView.scrollView.setContentOffset(CGPoint(x: 0.0, y: -self.webView.scrollView.contentInset.top), animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets)?
|
private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets, UIEdgeInsets)?
|
||||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition) {
|
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) {
|
||||||
self.validLayout = (size, insets, fullInsets)
|
self.validLayout = (size, insets, fullInsets, safeInsets)
|
||||||
|
|
||||||
self.previousScrollingOffset = ScrollingOffsetState(value: self.webView.scrollView.contentOffset.y, isDraggingOrDecelerating: self.webView.scrollView.isDragging || self.webView.scrollView.isDecelerating)
|
self.previousScrollingOffset = ScrollingOffsetState(value: self.webView.scrollView.contentOffset.y, isDraggingOrDecelerating: self.webView.scrollView.isDragging || self.webView.scrollView.isDecelerating)
|
||||||
|
|
||||||
let webViewFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: size.width - insets.left - insets.right, height: size.height - insets.top - fullInsets.bottom))
|
let webViewFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: size.width - insets.left - insets.right, height: size.height - insets.top))
|
||||||
var refresh = false
|
var refresh = false
|
||||||
if self.webView.frame.width > 0 && webViewFrame.width != self.webView.frame.width {
|
if self.webView.frame.width > 0 && webViewFrame.width != self.webView.frame.width {
|
||||||
refresh = true
|
refresh = true
|
||||||
@ -450,6 +463,9 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
self.webView.reloadInputViews()
|
self.webView.reloadInputViews()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.webView.scrollView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: fullInsets.bottom, right: 0.0)
|
||||||
|
self.webView.customBottomInset = max(insets.bottom, safeInsets.bottom)
|
||||||
|
|
||||||
self.webView.scrollView.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right)
|
self.webView.scrollView.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right)
|
||||||
self.webView.scrollView.horizontalScrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right)
|
self.webView.scrollView.horizontalScrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right)
|
||||||
|
|
||||||
@ -460,11 +476,12 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
ErrorComponent(
|
ErrorComponent(
|
||||||
theme: self.presentationData.theme,
|
theme: self.presentationData.theme,
|
||||||
title: self.presentationData.strings.Browser_ErrorTitle,
|
title: self.presentationData.strings.Browser_ErrorTitle,
|
||||||
text: error.localizedDescription
|
text: error.localizedDescription,
|
||||||
|
insets: insets
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: size.width - insets.left - insets.right - 72.0, height: size.height)
|
containerSize: CGSize(width: size.width, height: size.height)
|
||||||
)
|
)
|
||||||
if self.errorView.superview == nil {
|
if self.errorView.superview == nil {
|
||||||
self.addSubview(self.errorView)
|
self.addSubview(self.errorView)
|
||||||
@ -521,14 +538,25 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||||
if !decelerate {
|
if !decelerate {
|
||||||
self.snapScrollingOffsetToInsets()
|
self.snapScrollingOffsetToInsets()
|
||||||
|
|
||||||
|
if self.ignoreUpdatesUntilScrollingStopped {
|
||||||
|
self.ignoreUpdatesUntilScrollingStopped = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||||
self.snapScrollingOffsetToInsets()
|
self.snapScrollingOffsetToInsets()
|
||||||
|
|
||||||
|
if self.ignoreUpdatesUntilScrollingStopped {
|
||||||
|
self.ignoreUpdatesUntilScrollingStopped = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateScrollingOffset(isReset: Bool, transition: ComponentTransition) {
|
private func updateScrollingOffset(isReset: Bool, transition: ComponentTransition) {
|
||||||
|
guard !self.ignoreUpdatesUntilScrollingStopped else {
|
||||||
|
return
|
||||||
|
}
|
||||||
let scrollView = self.webView.scrollView
|
let scrollView = self.webView.scrollView
|
||||||
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
|
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
|
||||||
if let previousScrollingOffsetValue = self.previousScrollingOffset {
|
if let previousScrollingOffsetValue = self.previousScrollingOffset {
|
||||||
@ -558,6 +586,63 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var ignoreUpdatesUntilScrollingStopped = false
|
||||||
|
func resetScrolling() {
|
||||||
|
self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4))
|
||||||
|
if self.webView.scrollView.isDecelerating {
|
||||||
|
self.ignoreUpdatesUntilScrollingStopped = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 13.0, *)
|
||||||
|
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
|
||||||
|
if #available(iOS 14.5, *), navigationAction.shouldPerformDownload {
|
||||||
|
self.presentDownloadConfirmation(fileName: navigationAction.request.mainDocumentURL?.lastPathComponent ?? "file", proceed: { download in
|
||||||
|
if download {
|
||||||
|
decisionHandler(.download, preferences)
|
||||||
|
} else {
|
||||||
|
decisionHandler(.cancel, preferences)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if let url = navigationAction.request.url?.absoluteString {
|
||||||
|
if isTelegramMeLink(url) || isTelegraPhLink(url) {
|
||||||
|
decisionHandler(.cancel, preferences)
|
||||||
|
self.minimize()
|
||||||
|
self.openAppUrl(url)
|
||||||
|
} else {
|
||||||
|
decisionHandler(.allow, preferences)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decisionHandler(.allow, preferences)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
|
||||||
|
if navigationResponse.canShowMIMEType {
|
||||||
|
decisionHandler(.allow)
|
||||||
|
} else if #available(iOS 14.5, *) {
|
||||||
|
decisionHandler(.download)
|
||||||
|
} else {
|
||||||
|
decisionHandler(.cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||||
|
if let url = navigationAction.request.url?.absoluteString {
|
||||||
|
if isTelegramMeLink(url) || isTelegraPhLink(url) {
|
||||||
|
decisionHandler(.cancel)
|
||||||
|
self.minimize()
|
||||||
|
self.openAppUrl(url)
|
||||||
|
} else {
|
||||||
|
decisionHandler(.allow)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decisionHandler(.allow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
|
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
|
||||||
self.currentError = nil
|
self.currentError = nil
|
||||||
self.updateFontState(self.currentFontState, force: true)
|
self.updateFontState(self.currentFontState, force: true)
|
||||||
@ -578,8 +663,8 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
} else {
|
} else {
|
||||||
self.currentError = nil
|
self.currentError = nil
|
||||||
}
|
}
|
||||||
if let (size, insets, fullInsets) = self.validLayout {
|
if let (size, insets, fullInsets, safeInsets) = self.validLayout {
|
||||||
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: .immediate)
|
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: safeInsets, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,7 +681,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
self.close()
|
self.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOSApplicationExtension 15.0, iOS 15.0, *)
|
@available(iOS 15.0, *)
|
||||||
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
|
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
|
||||||
decisionHandler(.prompt)
|
decisionHandler(.prompt)
|
||||||
}
|
}
|
||||||
@ -699,12 +784,37 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||||||
completionHandler(configuration)
|
completionHandler(configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentDownloadConfirmation(fileName: String, proceed: @escaping (Bool) -> Void) {
|
||||||
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
var completed = false
|
||||||
|
let alertController = textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: presentationData.strings.WebBrowser_Download_Confirmation(fileName).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||||
|
if !completed {
|
||||||
|
completed = true
|
||||||
|
proceed(false)
|
||||||
|
}
|
||||||
|
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebBrowser_Download_Download, action: {
|
||||||
|
if !completed {
|
||||||
|
completed = true
|
||||||
|
proceed(true)
|
||||||
|
}
|
||||||
|
})])
|
||||||
|
alertController.dismissed = { byOutsideTap in
|
||||||
|
if byOutsideTap {
|
||||||
|
if !completed {
|
||||||
|
completed = true
|
||||||
|
proceed(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.present(alertController, nil)
|
||||||
|
}
|
||||||
|
|
||||||
private func open(url: String, new: Bool) {
|
private func open(url: String, new: Bool) {
|
||||||
let subject: BrowserScreen.Subject = .webPage(url: url)
|
let subject: BrowserScreen.Subject = .webPage(url: url)
|
||||||
if new, let navigationController = self.getNavigationController() {
|
if new, let navigationController = self.getNavigationController() {
|
||||||
navigationController._keepModalDismissProgress = true
|
navigationController._keepModalDismissProgress = true
|
||||||
self.minimize()
|
self.minimize()
|
||||||
let controller = BrowserScreen(context: self.context, subject: subject)
|
let controller = BrowserScreen(context: self.context, subject: subject, openPreviousOnClose: true)
|
||||||
navigationController._keepModalDismissProgress = true
|
navigationController._keepModalDismissProgress = true
|
||||||
navigationController.pushViewController(controller)
|
navigationController.pushViewController(controller)
|
||||||
} else {
|
} else {
|
||||||
@ -881,15 +991,18 @@ private final class ErrorComponent: CombinedComponent {
|
|||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let title: String
|
let title: String
|
||||||
let text: String
|
let text: String
|
||||||
|
let insets: UIEdgeInsets
|
||||||
|
|
||||||
init(
|
init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
title: String,
|
title: String,
|
||||||
text: String
|
text: String,
|
||||||
|
insets: UIEdgeInsets
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.title = title
|
self.title = title
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.insets = insets
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: ErrorComponent, rhs: ErrorComponent) -> Bool {
|
static func ==(lhs: ErrorComponent, rhs: ErrorComponent) -> Bool {
|
||||||
@ -902,10 +1015,14 @@ private final class ErrorComponent: CombinedComponent {
|
|||||||
if lhs.text != rhs.text {
|
if lhs.text != rhs.text {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.insets != rhs.insets {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
static var body: Body {
|
static var body: Body {
|
||||||
|
let background = Child(Rectangle.self)
|
||||||
let animation = Child(LottieComponent.self)
|
let animation = Child(LottieComponent.self)
|
||||||
let title = Child(MultilineTextComponent.self)
|
let title = Child(MultilineTextComponent.self)
|
||||||
let text = Child(MultilineTextComponent.self)
|
let text = Child(MultilineTextComponent.self)
|
||||||
@ -916,6 +1033,17 @@ private final class ErrorComponent: CombinedComponent {
|
|||||||
let animationSpacing: CGFloat = 8.0
|
let animationSpacing: CGFloat = 8.0
|
||||||
let textSpacing: CGFloat = 8.0
|
let textSpacing: CGFloat = 8.0
|
||||||
|
|
||||||
|
let constrainedWidth = context.availableSize.width - 76.0 - context.component.insets.left - context.component.insets.right
|
||||||
|
|
||||||
|
let background = background.update(
|
||||||
|
component: Rectangle(color: context.component.theme.list.plainBackgroundColor),
|
||||||
|
availableSize: context.availableSize,
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
context.add(background
|
||||||
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||||
|
)
|
||||||
|
|
||||||
let animation = animation.update(
|
let animation = animation.update(
|
||||||
component: LottieComponent(
|
component: LottieComponent(
|
||||||
content: LottieComponent.AppBundleContent(name: "ChatListNoResults")
|
content: LottieComponent.AppBundleContent(name: "ChatListNoResults")
|
||||||
@ -924,9 +1052,6 @@ private final class ErrorComponent: CombinedComponent {
|
|||||||
availableSize: CGSize(width: animationSize, height: animationSize),
|
availableSize: CGSize(width: animationSize, height: animationSize),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
context.add(animation
|
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + animation.size.height / 2.0))
|
|
||||||
)
|
|
||||||
contentHeight += animation.size.height + animationSpacing
|
contentHeight += animation.size.height + animationSpacing
|
||||||
|
|
||||||
let title = title.update(
|
let title = title.update(
|
||||||
@ -939,12 +1064,9 @@ private final class ErrorComponent: CombinedComponent {
|
|||||||
horizontalAlignment: .center
|
horizontalAlignment: .center
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
availableSize: context.availableSize,
|
availableSize: CGSize(width: constrainedWidth, height: context.availableSize.height),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
context.add(title
|
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + title.size.height / 2.0))
|
|
||||||
)
|
|
||||||
contentHeight += title.size.height + textSpacing
|
contentHeight += title.size.height + textSpacing
|
||||||
|
|
||||||
let text = text.update(
|
let text = text.update(
|
||||||
@ -958,15 +1080,27 @@ private final class ErrorComponent: CombinedComponent {
|
|||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
availableSize: context.availableSize,
|
availableSize: CGSize(width: constrainedWidth, height: context.availableSize.height),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
context.add(text
|
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + text.size.height / 2.0))
|
|
||||||
)
|
|
||||||
contentHeight += text.size.height
|
contentHeight += text.size.height
|
||||||
|
|
||||||
return CGSize(width: context.availableSize.width, height: contentHeight)
|
var originY = floor((context.availableSize.height - contentHeight) / 2.0)
|
||||||
|
context.add(animation
|
||||||
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + animation.size.height / 2.0))
|
||||||
|
)
|
||||||
|
originY += animation.size.height + animationSpacing
|
||||||
|
|
||||||
|
context.add(title
|
||||||
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + title.size.height / 2.0))
|
||||||
|
)
|
||||||
|
originY += title.size.height + textSpacing
|
||||||
|
|
||||||
|
context.add(text
|
||||||
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + text.size.height / 2.0))
|
||||||
|
)
|
||||||
|
|
||||||
|
return context.availableSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
317
submodules/BrowserUI/Sources/Punycode.swift
Normal file
317
submodules/BrowserUI/Sources/Punycode.swift
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
//
|
||||||
|
// Created by kojirof on 2018-11-19.
|
||||||
|
// Copyright (c) 2018 Gumob. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
//MIT License
|
||||||
|
//
|
||||||
|
//Copyright (c) 2018 Gumob
|
||||||
|
//
|
||||||
|
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
//of this software and associated documentation files (the "Software"), to deal
|
||||||
|
//in the Software without restriction, including without limitation the rights
|
||||||
|
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
//copies of the Software, and to permit persons to whom the Software is
|
||||||
|
//furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
//The above copyright notice and this permission notice shall be included in all
|
||||||
|
//copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
//SOFTWARE.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Punycode {
|
||||||
|
|
||||||
|
/// Punycode RFC 3492
|
||||||
|
/// See https://www.ietf.org/rfc/rfc3492.txt for standard details
|
||||||
|
|
||||||
|
private let base: Int = 36
|
||||||
|
private let tMin: Int = 1
|
||||||
|
private let tMax: Int = 26
|
||||||
|
private let skew: Int = 38
|
||||||
|
private let damp: Int = 700
|
||||||
|
private let initialBias: Int = 72
|
||||||
|
private let initialN: Int = 128
|
||||||
|
|
||||||
|
/// RFC 3492 specific
|
||||||
|
private let delimiter: Character = "-"
|
||||||
|
private let lowercase: ClosedRange<Character> = "a"..."z"
|
||||||
|
private let digits: ClosedRange<Character> = "0"..."9"
|
||||||
|
private let lettersBase: UInt32 = Character("a").unicodeScalars.first!.value
|
||||||
|
private let digitsBase: UInt32 = Character("0").unicodeScalars.first!.value
|
||||||
|
|
||||||
|
/// IDNA
|
||||||
|
private let ace: String = "xn--"
|
||||||
|
|
||||||
|
private func adaptBias(_ delta: Int, _ numberOfPoints: Int, _ firstTime: Bool) -> Int {
|
||||||
|
var delta: Int = delta
|
||||||
|
if firstTime {
|
||||||
|
delta /= damp
|
||||||
|
} else {
|
||||||
|
delta /= 2
|
||||||
|
}
|
||||||
|
delta += delta / numberOfPoints
|
||||||
|
var k: Int = 0
|
||||||
|
while delta > ((base - tMin) * tMax) / 2 {
|
||||||
|
delta /= base - tMin
|
||||||
|
k += base
|
||||||
|
}
|
||||||
|
return k + ((base - tMin + 1) * delta) / (delta + skew)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps a punycode character to index
|
||||||
|
private func punycodeIndex(for character: Character) -> Int? {
|
||||||
|
if lowercase.contains(character) {
|
||||||
|
return Int(character.unicodeScalars.first!.value - lettersBase)
|
||||||
|
} else if digits.contains(character) {
|
||||||
|
return Int(character.unicodeScalars.first!.value - digitsBase) + 26 /// count of lowercase letters range
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps an index to corresponding punycode character
|
||||||
|
private func punycodeValue(for digit: Int) -> Character? {
|
||||||
|
guard digit < base else { return nil }
|
||||||
|
if digit < 26 {
|
||||||
|
return Character(UnicodeScalar(lettersBase.advanced(by: digit))!)
|
||||||
|
} else {
|
||||||
|
return Character(UnicodeScalar(digitsBase.advanced(by: digit - 26))!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes punycode encoded string to original representation
|
||||||
|
///
|
||||||
|
/// - Parameter punycode: Punycode encoding (RFC 3492)
|
||||||
|
/// - Returns: Decoded string or nil if the input cannot be decoded
|
||||||
|
public func decodePunycode(_ punycode: Substring) -> String? {
|
||||||
|
var n: Int = initialN
|
||||||
|
var i: Int = 0
|
||||||
|
var bias: Int = initialBias
|
||||||
|
var output: [Character] = []
|
||||||
|
var inputPosition = punycode.startIndex
|
||||||
|
|
||||||
|
let delimiterPosition: Substring.Index = punycode.lastIndex(of: delimiter) ?? punycode.startIndex
|
||||||
|
if delimiterPosition > punycode.startIndex {
|
||||||
|
output.append(contentsOf: punycode[..<delimiterPosition])
|
||||||
|
inputPosition = punycode.index(after: delimiterPosition)
|
||||||
|
}
|
||||||
|
var punycodeInput: Substring = punycode[inputPosition..<punycode.endIndex]
|
||||||
|
while !punycodeInput.isEmpty {
|
||||||
|
let oldI: Int = i
|
||||||
|
var w: Int = 1
|
||||||
|
var k: Int = base
|
||||||
|
repeat {
|
||||||
|
let character: Character = punycodeInput.removeFirst()
|
||||||
|
guard let digit: Int = punycodeIndex(for: character) else {
|
||||||
|
return nil /// Failing on badly formatted punycode
|
||||||
|
}
|
||||||
|
i += digit * w
|
||||||
|
let t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias)
|
||||||
|
if digit < t {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
w *= base - t
|
||||||
|
k += base
|
||||||
|
} while !punycodeInput.isEmpty
|
||||||
|
bias = adaptBias(i - oldI, output.count + 1, oldI == 0)
|
||||||
|
n += i / (output.count + 1)
|
||||||
|
i %= (output.count + 1)
|
||||||
|
guard n >= 0x80, let scalar = UnicodeScalar(n) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
output.insert(Character(scalar), at: i)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes string to punycode (RFC 3492)
|
||||||
|
///
|
||||||
|
/// - Parameter input: Input string
|
||||||
|
/// - Returns: Punycode encoded string
|
||||||
|
public func encodePunycode(_ input: Substring) -> String? {
|
||||||
|
var n: Int = initialN
|
||||||
|
var delta: Int = 0
|
||||||
|
var bias: Int = initialBias
|
||||||
|
var output: String = ""
|
||||||
|
for scalar in input.unicodeScalars {
|
||||||
|
if scalar.isASCII {
|
||||||
|
let char = Character(scalar)
|
||||||
|
output.append(char)
|
||||||
|
} else if !scalar.isValid {
|
||||||
|
return nil /// Encountered a scalar out of acceptable range
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var handled: Int = output.count
|
||||||
|
let basic: Int = handled
|
||||||
|
if basic > 0 {
|
||||||
|
output.append(delimiter)
|
||||||
|
}
|
||||||
|
while handled < input.unicodeScalars.count {
|
||||||
|
var minimumCodepoint: Int = 0x10FFFF
|
||||||
|
for scalar: Unicode.Scalar in input.unicodeScalars {
|
||||||
|
if scalar.value < minimumCodepoint && scalar.value >= n {
|
||||||
|
minimumCodepoint = Int(scalar.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delta += (minimumCodepoint - n) * (handled + 1)
|
||||||
|
n = minimumCodepoint
|
||||||
|
for scalar: Unicode.Scalar in input.unicodeScalars {
|
||||||
|
if scalar.value < n {
|
||||||
|
delta += 1
|
||||||
|
} else if scalar.value == n {
|
||||||
|
var q: Int = delta
|
||||||
|
var k: Int = base
|
||||||
|
while true {
|
||||||
|
let t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias)
|
||||||
|
if q < t {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
guard let character: Character = punycodeValue(for: t + ((q - t) % (base - t))) else { return nil }
|
||||||
|
output.append(character)
|
||||||
|
q = (q - t) / (base - t)
|
||||||
|
k += base
|
||||||
|
}
|
||||||
|
guard let character: Character = punycodeValue(for: q) else { return nil }
|
||||||
|
output.append(character)
|
||||||
|
bias = adaptBias(delta, handled + 1, handled == basic)
|
||||||
|
delta = 0
|
||||||
|
handled += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delta += 1
|
||||||
|
n += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns new string containing IDNA-encoded hostname
|
||||||
|
///
|
||||||
|
/// - Returns: IDNA encoded hostname or nil if the string can't be encoded
|
||||||
|
public func encodeIDNA(_ input: Substring) -> String? {
|
||||||
|
let parts: [Substring] = input.split(separator: ".")
|
||||||
|
var output: String = ""
|
||||||
|
for part: Substring in parts {
|
||||||
|
if output.count > 0 {
|
||||||
|
output.append(".")
|
||||||
|
}
|
||||||
|
if part.rangeOfCharacter(from: CharacterSet.urlHostAllowed.inverted) != nil {
|
||||||
|
guard let encoded: String = part.lowercased().punycodeEncoded else { return nil }
|
||||||
|
output += ace + encoded
|
||||||
|
} else {
|
||||||
|
output += part
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns new string containing hostname decoded from IDNA representation
|
||||||
|
///
|
||||||
|
/// - Returns: Original hostname or nil if the string doesn't contain correct encoding
|
||||||
|
public func decodedIDNA(_ input: Substring) -> String? {
|
||||||
|
let parts: [Substring] = input.split(separator: ".")
|
||||||
|
var output: String = ""
|
||||||
|
for part: Substring in parts {
|
||||||
|
if output.count > 0 {
|
||||||
|
output.append(".")
|
||||||
|
}
|
||||||
|
if part.hasPrefix(ace) {
|
||||||
|
guard let decoded: String = part.dropFirst(ace.count).punycodeDecoded else { return nil }
|
||||||
|
output += decoded
|
||||||
|
} else {
|
||||||
|
output += part
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Substring {
|
||||||
|
func lastIndex(of element: Character) -> String.Index? {
|
||||||
|
var position: Index = endIndex
|
||||||
|
while position > startIndex {
|
||||||
|
position = self.index(before: position)
|
||||||
|
if self[position] == element {
|
||||||
|
return position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension UnicodeScalar {
|
||||||
|
var isValid: Bool {
|
||||||
|
return value < 0xD880 || (value >= 0xE000 && value <= 0x1FFFFF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Substring {
|
||||||
|
/// Returns new string in punycode encoding (RFC 3492)
|
||||||
|
///
|
||||||
|
/// - Returns: Punycode encoded string or nil if the string can't be encoded
|
||||||
|
var punycodeEncoded: String? {
|
||||||
|
return Punycode().encodePunycode(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns new string decoded from punycode representation (RFC 3492)
|
||||||
|
///
|
||||||
|
/// - Returns: Original string or nil if the string doesn't contain correct encoding
|
||||||
|
var punycodeDecoded: String? {
|
||||||
|
return Punycode().decodePunycode(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns new string containing IDNA-encoded hostname
|
||||||
|
///
|
||||||
|
/// - Returns: IDNA encoded hostname or nil if the string can't be encoded
|
||||||
|
var idnaEncoded: String? {
|
||||||
|
return Punycode().encodeIDNA(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns new string containing hostname decoded from IDNA representation
|
||||||
|
///
|
||||||
|
/// - Returns: Original hostname or nil if the string doesn't contain correct encoding
|
||||||
|
var idnaDecoded: String? {
|
||||||
|
return Punycode().decodedIDNA(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension String {
|
||||||
|
|
||||||
|
/// Returns new string in punycode encoding (RFC 3492)
|
||||||
|
///
|
||||||
|
/// - Returns: Punycode encoded string or nil if the string can't be encoded
|
||||||
|
var punycodeEncoded: String? {
|
||||||
|
return self[..<self.endIndex].punycodeEncoded
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns new string decoded from punycode representation (RFC 3492)
|
||||||
|
///
|
||||||
|
/// - Returns: Original string or nil if the string doesn't contain correct encoding
|
||||||
|
var punycodeDecoded: String? {
|
||||||
|
return self[..<self.endIndex].punycodeDecoded
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns new string containing IDNA-encoded hostname
|
||||||
|
///
|
||||||
|
/// - Returns: IDNA encoded hostname or nil if the string can't be encoded
|
||||||
|
var idnaEncoded: String? {
|
||||||
|
return self[..<self.endIndex].idnaEncoded
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns new string containing hostname decoded from IDNA representation
|
||||||
|
///
|
||||||
|
/// - Returns: Original hostname or nil if the string doesn't contain correct encoding
|
||||||
|
var idnaDecoded: String? {
|
||||||
|
return self[..<self.endIndex].idnaDecoded
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ final class SectionHeaderComponent: Component {
|
|||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let style: Style
|
let style: Style
|
||||||
let title: String
|
let title: String
|
||||||
|
let insets: UIEdgeInsets
|
||||||
let actionTitle: String?
|
let actionTitle: String?
|
||||||
let action: (() -> Void)?
|
let action: (() -> Void)?
|
||||||
|
|
||||||
@ -20,12 +21,14 @@ final class SectionHeaderComponent: Component {
|
|||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
style: Style,
|
style: Style,
|
||||||
title: String,
|
title: String,
|
||||||
|
insets: UIEdgeInsets,
|
||||||
actionTitle: String?,
|
actionTitle: String?,
|
||||||
action: (() -> Void)?
|
action: (() -> Void)?
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.style = style
|
self.style = style
|
||||||
self.title = title
|
self.title = title
|
||||||
|
self.insets = insets
|
||||||
self.actionTitle = actionTitle
|
self.actionTitle = actionTitle
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
@ -40,6 +43,9 @@ final class SectionHeaderComponent: Component {
|
|||||||
if lhs.title != rhs.title {
|
if lhs.title != rhs.title {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.insets != rhs.insets {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.actionTitle != rhs.actionTitle {
|
if lhs.actionTitle != rhs.actionTitle {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -73,7 +79,7 @@ final class SectionHeaderComponent: Component {
|
|||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
let height: CGFloat = 28.0
|
let height: CGFloat = 28.0
|
||||||
let leftInset: CGFloat = 16.0
|
let leftInset: CGFloat = 16.0 + component.insets.left
|
||||||
let rightInset: CGFloat = 0.0
|
let rightInset: CGFloat = 0.0
|
||||||
|
|
||||||
let previousTitleFrame = self.title.view?.frame
|
let previousTitleFrame = self.title.view?.frame
|
||||||
|
|||||||
@ -88,8 +88,7 @@ private final class ItemNode: ASDisplayNode {
|
|||||||
title = presentationData.strings.ChatList_Search_FilterChannels
|
title = presentationData.strings.ChatList_Search_FilterChannels
|
||||||
icon = nil
|
icon = nil
|
||||||
case .apps:
|
case .apps:
|
||||||
//TODO:localize
|
title = presentationData.strings.ChatList_Search_FilterApps
|
||||||
title = "Apps"
|
|
||||||
icon = nil
|
icon = nil
|
||||||
case .media:
|
case .media:
|
||||||
title = presentationData.strings.ChatList_Search_FilterMedia
|
title = presentationData.strings.ChatList_Search_FilterMedia
|
||||||
|
|||||||
@ -254,16 +254,15 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if case .apps = key {
|
} else if case .apps = key {
|
||||||
//TODO:localize
|
|
||||||
if case .popularApps = section {
|
if case .popularApps = section {
|
||||||
header = ChatListSearchItemHeader(type: .text("POPULAR APPS", 1), theme: theme, strings: strings)
|
header = ChatListSearchItemHeader(type: .text(presentationData.strings.ChatList_Search_SectionPopularApps, 1), theme: theme, strings: strings)
|
||||||
} else {
|
} else {
|
||||||
if let isChannelsTabExpanded {
|
if let isChannelsTabExpanded {
|
||||||
header = ChatListSearchItemHeader(type: .text("APPS YOU USE", 0), theme: theme, strings: strings, actionTitle: isChannelsTabExpanded ? presentationData.strings.ChatList_Search_SectionActionShowLess : presentationData.strings.ChatList_Search_SectionActionShowMore, action: {
|
header = ChatListSearchItemHeader(type: .text(presentationData.strings.ChatList_Search_SectionRecentApps, 0), theme: theme, strings: strings, actionTitle: isChannelsTabExpanded ? presentationData.strings.ChatList_Search_SectionActionShowLess : presentationData.strings.ChatList_Search_SectionActionShowMore, action: {
|
||||||
toggleChannelsTabExpanded()
|
toggleChannelsTabExpanded()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
header = ChatListSearchItemHeader(type: .text("APPS YOU USE", 0), theme: theme, strings: strings, actionTitle: nil, action: nil)
|
header = ChatListSearchItemHeader(type: .text(presentationData.strings.ChatList_Search_SectionRecentApps, 0), theme: theme, strings: strings, actionTitle: nil, action: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1454,8 +1453,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
if key == .channels {
|
if key == .channels {
|
||||||
emptyRecentTextNode.attributedText = NSAttributedString(string: presentationData.strings.ChatList_Search_RecommendedChannelsEmpty_Text, font: Font.regular(15.0), textColor: presentationData.theme.list.freeTextColor)
|
emptyRecentTextNode.attributedText = NSAttributedString(string: presentationData.strings.ChatList_Search_RecommendedChannelsEmpty_Text, font: Font.regular(15.0), textColor: presentationData.theme.list.freeTextColor)
|
||||||
} else if key == .apps {
|
} else if key == .apps {
|
||||||
//TODO:localize
|
emptyRecentTextNode.attributedText = NSAttributedString(string: presentationData.strings.ChatList_Search_Apps_Empty_Text, font: Font.regular(15.0), textColor: presentationData.theme.list.freeTextColor)
|
||||||
emptyRecentTextNode.attributedText = NSAttributedString(string: "No Apps Found", font: Font.regular(15.0), textColor: presentationData.theme.list.freeTextColor)
|
|
||||||
}
|
}
|
||||||
self.emptyRecentTextNode = emptyRecentTextNode
|
self.emptyRecentTextNode = emptyRecentTextNode
|
||||||
|
|
||||||
@ -3417,7 +3415,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let peerNotificationSettings = notificationSettings[id]
|
let peerNotificationSettings = notificationSettings[id]
|
||||||
//TODO:localize
|
|
||||||
let subpeerSummary: RecentlySearchedPeerSubpeerSummary? = nil
|
let subpeerSummary: RecentlySearchedPeerSubpeerSummary? = nil
|
||||||
var peerStoryStats: PeerStoryStats?
|
var peerStoryStats: PeerStoryStats?
|
||||||
if let value = storyStats[peer.id] {
|
if let value = storyStats[peer.id] {
|
||||||
|
|||||||
@ -552,7 +552,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var contentBottomOffset: CGFloat {
|
var contentBottomOffset: CGFloat {
|
||||||
return -self.scrollView.contentOffset.y + self.scrollView.contentSize.height
|
let bottomInset = self.layout?.containerLayout.insets.bottom ?? 0.0
|
||||||
|
return -self.scrollView.contentOffset.y + self.scrollView.contentSize.height - bottomInset
|
||||||
}
|
}
|
||||||
|
|
||||||
let coveringOffsetUpdated: (Viewport, ContainedViewLayoutTransition) -> Void
|
let coveringOffsetUpdated: (Viewport, ContainedViewLayoutTransition) -> Void
|
||||||
|
|||||||
@ -2049,6 +2049,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
|||||||
let peer = Promise<EnginePeer?>()
|
let peer = Promise<EnginePeer?>()
|
||||||
peer.set(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
|
peer.set(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
|
||||||
|
|
||||||
|
let canViewStatsValue = Atomic<Bool>(value: true)
|
||||||
let peerData = context.engine.data.get(
|
let peerData = context.engine.data.get(
|
||||||
TelegramEngine.EngineData.Item.Peer.CanViewStats(id: peerId),
|
TelegramEngine.EngineData.Item.Peer.CanViewStats(id: peerId),
|
||||||
TelegramEngine.EngineData.Item.Peer.AdsRestricted(id: peerId),
|
TelegramEngine.EngineData.Item.Peer.AdsRestricted(id: peerId),
|
||||||
@ -2081,6 +2082,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
|||||||
|> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, revenueState, revenueTransactions, starsState, starsTransactions, peerData, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, revenueState, revenueTransactions, starsState, starsTransactions, peerData, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||||
let (canViewStats, adsRestricted, canViewRevenue, canViewStarsRevenue) = peerData
|
let (canViewStats, adsRestricted, canViewRevenue, canViewStarsRevenue) = peerData
|
||||||
|
|
||||||
|
let _ = canViewStatsValue.swap(canViewStats)
|
||||||
|
|
||||||
var isGroup = false
|
var isGroup = false
|
||||||
if let peer, case let .channel(channel) = peer, case .group = channel.info {
|
if let peer, case let .channel(channel) = peer, case .group = channel.info {
|
||||||
isGroup = true
|
isGroup = true
|
||||||
@ -2157,9 +2160,17 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
|||||||
case .stats:
|
case .stats:
|
||||||
index = 0
|
index = 0
|
||||||
case .boosts:
|
case .boosts:
|
||||||
index = 1
|
if canViewStats {
|
||||||
|
index = 1
|
||||||
|
} else {
|
||||||
|
index = 0
|
||||||
|
}
|
||||||
case .monetization:
|
case .monetization:
|
||||||
index = 2
|
if canViewStats {
|
||||||
|
index = 2
|
||||||
|
} else {
|
||||||
|
index = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var tabs: [String] = []
|
var tabs: [String] = []
|
||||||
if canViewStats {
|
if canViewStats {
|
||||||
@ -2195,12 +2206,21 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
|||||||
}
|
}
|
||||||
controller.titleControlValueChanged = { value in
|
controller.titleControlValueChanged = { value in
|
||||||
updateState { state in
|
updateState { state in
|
||||||
|
let canViewStats = canViewStatsValue.with { $0 }
|
||||||
let section: ChannelStatsSection
|
let section: ChannelStatsSection
|
||||||
switch value {
|
switch value {
|
||||||
case 0:
|
case 0:
|
||||||
section = .stats
|
if canViewStats {
|
||||||
|
section = .stats
|
||||||
|
} else {
|
||||||
|
section = .boosts
|
||||||
|
}
|
||||||
case 1:
|
case 1:
|
||||||
section = .boosts
|
if canViewStats {
|
||||||
|
section = .boosts
|
||||||
|
} else {
|
||||||
|
section = .monetization
|
||||||
|
}
|
||||||
case 2:
|
case 2:
|
||||||
section = .monetization
|
section = .monetization
|
||||||
let _ = (ApplicationSpecificNotice.monetizationIntroDismissed(accountManager: context.sharedContext.accountManager)
|
let _ = (ApplicationSpecificNotice.monetizationIntroDismissed(accountManager: context.sharedContext.accountManager)
|
||||||
|
|||||||
@ -2627,8 +2627,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
|||||||
|
|
||||||
if !isScheduled && canSpeak {
|
if !isScheduled && canSpeak {
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
//TODO:localize
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_MicrophoneModes, textColor: .primary, icon: { theme in
|
||||||
items.append(.action(ContextMenuActionItem(text: "Microphone Modes", textColor: .primary, icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Noise"), color: theme.actionSheet.primaryTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Noise"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|||||||
@ -520,7 +520,7 @@ public final class MediaEditor {
|
|||||||
self.renderer.consume(main: .texture(texture, time, hasTransparency), additional: additionalTexture.flatMap { .texture($0, time, false) }, render: true, displayEnabled: false)
|
self.renderer.consume(main: .texture(texture, time, hasTransparency), additional: additionalTexture.flatMap { .texture($0, time, false) }, render: true, displayEnabled: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupSource() {
|
private func setupSource(andPlay: Bool) {
|
||||||
guard let renderTarget = self.previewView else {
|
guard let renderTarget = self.previewView else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -830,6 +830,9 @@ public final class MediaEditor {
|
|||||||
self.setupTimeObservers()
|
self.setupTimeObservers()
|
||||||
Queue.mainQueue().justDispatch {
|
Queue.mainQueue().justDispatch {
|
||||||
let startPlayback = {
|
let startPlayback = {
|
||||||
|
guard andPlay else {
|
||||||
|
return
|
||||||
|
}
|
||||||
player.playImmediately(atRate: 1.0)
|
player.playImmediately(atRate: 1.0)
|
||||||
// additionalPlayer?.playImmediately(atRate: 1.0)
|
// additionalPlayer?.playImmediately(atRate: 1.0)
|
||||||
self.audioPlayer?.playImmediately(atRate: 1.0)
|
self.audioPlayer?.playImmediately(atRate: 1.0)
|
||||||
@ -941,13 +944,13 @@ public final class MediaEditor {
|
|||||||
self.audioDelayTimer = nil
|
self.audioDelayTimer = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func attachPreviewView(_ previewView: MediaEditorPreviewView) {
|
public func attachPreviewView(_ previewView: MediaEditorPreviewView, andPlay: Bool) {
|
||||||
self.previewView?.renderer = nil
|
self.previewView?.renderer = nil
|
||||||
|
|
||||||
self.previewView = previewView
|
self.previewView = previewView
|
||||||
previewView.renderer = self.renderer
|
previewView.renderer = self.renderer
|
||||||
|
|
||||||
self.setupSource()
|
self.setupSource(andPlay: andPlay)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var skipRendering = false
|
private var skipRendering = false
|
||||||
@ -1118,8 +1121,9 @@ public final class MediaEditor {
|
|||||||
self.initialSeekPosition = position
|
self.initialSeekPosition = position
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.renderer.setRate(1.0)
|
if play {
|
||||||
if !play {
|
self.renderer.setRate(1.0)
|
||||||
|
} else {
|
||||||
self.player?.pause()
|
self.player?.pause()
|
||||||
self.additionalPlayer?.pause()
|
self.additionalPlayer?.pause()
|
||||||
self.audioPlayer?.pause()
|
self.audioPlayer?.pause()
|
||||||
|
|||||||
@ -64,6 +64,7 @@ swift_library(
|
|||||||
"//submodules/WebsiteType",
|
"//submodules/WebsiteType",
|
||||||
"//submodules/UrlEscaping",
|
"//submodules/UrlEscaping",
|
||||||
"//submodules/DeviceLocationManager",
|
"//submodules/DeviceLocationManager",
|
||||||
|
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -225,7 +225,7 @@ private final class MediaCoverScreenComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(Button(
|
component: AnyComponent(Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "Cancel", font: Font.regular(17.0), textColor: .white)))
|
MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Common_Cancel, font: Font.regular(17.0), textColor: .white)))
|
||||||
),
|
),
|
||||||
action: { [weak controller] in
|
action: { [weak controller] in
|
||||||
controller?.requestDismiss(animated: true)
|
controller?.requestDismiss(animated: true)
|
||||||
@ -546,6 +546,7 @@ final class MediaCoverScreen: ViewController {
|
|||||||
fileprivate let mediaEditor: Signal<MediaEditor?, NoError>
|
fileprivate let mediaEditor: Signal<MediaEditor?, NoError>
|
||||||
fileprivate let previewView: MediaEditorPreviewView
|
fileprivate let previewView: MediaEditorPreviewView
|
||||||
fileprivate let portalView: PortalView
|
fileprivate let portalView: PortalView
|
||||||
|
fileprivate let exclusive: Bool
|
||||||
|
|
||||||
func withMediaEditor(_ f: @escaping (MediaEditor) -> Void) {
|
func withMediaEditor(_ f: @escaping (MediaEditor) -> Void) {
|
||||||
let _ = (self.mediaEditor
|
let _ = (self.mediaEditor
|
||||||
@ -564,12 +565,14 @@ final class MediaCoverScreen: ViewController {
|
|||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
mediaEditor: Signal<MediaEditor?, NoError>,
|
mediaEditor: Signal<MediaEditor?, NoError>,
|
||||||
previewView: MediaEditorPreviewView,
|
previewView: MediaEditorPreviewView,
|
||||||
portalView: PortalView
|
portalView: PortalView,
|
||||||
|
exclusive: Bool
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mediaEditor = mediaEditor
|
self.mediaEditor = mediaEditor
|
||||||
self.previewView = previewView
|
self.previewView = previewView
|
||||||
self.portalView = portalView
|
self.portalView = portalView
|
||||||
|
self.exclusive = exclusive
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
self.navigationPresentation = .flatModal
|
self.navigationPresentation = .flatModal
|
||||||
@ -601,7 +604,9 @@ final class MediaCoverScreen: ViewController {
|
|||||||
self.dismissed()
|
self.dismissed()
|
||||||
|
|
||||||
self.node.animateOutToEditor(completion: {
|
self.node.animateOutToEditor(completion: {
|
||||||
self.dismiss()
|
if !self.exclusive {
|
||||||
|
self.dismiss()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -48,6 +48,7 @@ import StickerPackEditTitleController
|
|||||||
import StickerPickerScreen
|
import StickerPickerScreen
|
||||||
import UIKitRuntimeUtils
|
import UIKitRuntimeUtils
|
||||||
import ImageObjectSeparation
|
import ImageObjectSeparation
|
||||||
|
import SaveProgressScreen
|
||||||
|
|
||||||
private let playbackButtonTag = GenericComponentViewTag()
|
private let playbackButtonTag = GenericComponentViewTag()
|
||||||
private let muteButtonTag = GenericComponentViewTag()
|
private let muteButtonTag = GenericComponentViewTag()
|
||||||
@ -2809,7 +2810,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
})
|
})
|
||||||
|
|
||||||
if controller.isEditingStoryCover {
|
if controller.isEditingStoryCover {
|
||||||
self.openCoverSelection(immediate: true)
|
Queue.mainQueue().justDispatch {
|
||||||
|
self.openCoverSelection(exclusive: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2984,7 +2987,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
self.controller?.stickerRecommendedEmoji = emojiForClasses(classes.map { $0.0 })
|
self.controller?.stickerRecommendedEmoji = emojiForClasses(classes.map { $0.0 })
|
||||||
}
|
}
|
||||||
mediaEditor.attachPreviewView(self.previewView)
|
mediaEditor.attachPreviewView(self.previewView, andPlay: !(self.controller?.isEditingStoryCover ?? false))
|
||||||
|
|
||||||
if case .empty = effectiveSubject {
|
if case .empty = effectiveSubject {
|
||||||
self.stickerMaskDrawingView?.emptyColor = .black
|
self.stickerMaskDrawingView?.emptyColor = .black
|
||||||
@ -4704,7 +4707,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func openCoverSelection(immediate: Bool) {
|
func openCoverSelection(exclusive: Bool) {
|
||||||
guard let portalView = PortalView(matchPosition: false) else {
|
guard let portalView = PortalView(matchPosition: false) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -4721,12 +4724,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
context: self.context,
|
context: self.context,
|
||||||
mediaEditor: self.mediaEditorPromise.get(),
|
mediaEditor: self.mediaEditorPromise.get(),
|
||||||
previewView: self.previewView,
|
previewView: self.previewView,
|
||||||
portalView: portalView
|
portalView: portalView,
|
||||||
|
exclusive: exclusive
|
||||||
)
|
)
|
||||||
coverController.dismissed = { [weak self] in
|
coverController.dismissed = { [weak self] in
|
||||||
if let self {
|
if let self {
|
||||||
self.animateInFromTool()
|
if exclusive {
|
||||||
self.requestCompletion(playHaptic: false)
|
self.controller?.requestDismiss(saveDraft: false, animated: true)
|
||||||
|
} else {
|
||||||
|
self.animateInFromTool()
|
||||||
|
self.requestCompletion(playHaptic: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
coverController.completed = { [weak self] position, image in
|
coverController.completed = { [weak self] position, image in
|
||||||
@ -4737,7 +4745,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
self.controller?.present(coverController, in: .current)
|
self.controller?.present(coverController, in: .current)
|
||||||
self.coverScreen = coverController
|
self.coverScreen = coverController
|
||||||
|
|
||||||
if immediate {
|
if exclusive {
|
||||||
self.isDisplayingTool = .cover
|
self.isDisplayingTool = .cover
|
||||||
self.requestUpdate(transition: .immediate)
|
self.requestUpdate(transition: .immediate)
|
||||||
} else {
|
} else {
|
||||||
@ -5226,7 +5234,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
self.controller?.present(controller, in: .window(.root))
|
self.controller?.present(controller, in: .window(.root))
|
||||||
self.animateOutToTool(tool: .tools)
|
self.animateOutToTool(tool: .tools)
|
||||||
case .cover:
|
case .cover:
|
||||||
self.openCoverSelection(immediate: false)
|
self.openCoverSelection(exclusive: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -5923,7 +5931,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
|
|
||||||
editCoverImpl = { [weak self, weak controller] in
|
editCoverImpl = { [weak self, weak controller] in
|
||||||
if let self {
|
if let self {
|
||||||
self.node.openCoverSelection(immediate: false)
|
self.node.openCoverSelection(exclusive: false)
|
||||||
}
|
}
|
||||||
if let controller {
|
if let controller {
|
||||||
controller.dismiss()
|
controller.dismiss()
|
||||||
|
|||||||
@ -246,8 +246,7 @@ final class MiniAppListScreenComponent: Component {
|
|||||||
) -> CGFloat {
|
) -> CGFloat {
|
||||||
let rightButtons: [AnyComponentWithIdentity<NavigationButtonComponentEnvironment>] = []
|
let rightButtons: [AnyComponentWithIdentity<NavigationButtonComponentEnvironment>] = []
|
||||||
|
|
||||||
//TODO:localize
|
let titleText: String = strings.MiniAppList_Title
|
||||||
let titleText: String = "Examples"
|
|
||||||
|
|
||||||
let closeTitle: String = strings.Common_Close
|
let closeTitle: String = strings.Common_Close
|
||||||
let headerContent: ChatListHeaderComponent.Content? = ChatListHeaderComponent.Content(
|
let headerContent: ChatListHeaderComponent.Content? = ChatListHeaderComponent.Content(
|
||||||
@ -316,12 +315,11 @@ final class MiniAppListScreenComponent: Component {
|
|||||||
containerSize: size
|
containerSize: size
|
||||||
)
|
)
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let sectionHeaderSize = self.sectionHeader.update(
|
let sectionHeaderSize = self.sectionHeader.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(ListHeaderComponent(
|
component: AnyComponent(ListHeaderComponent(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
title: "APPS THAT ACCEPT STARS"
|
title: strings.MiniAppList_ListSectionHeader
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: size.width, height: 1000.0)
|
containerSize: CGSize(width: size.width, height: 1000.0)
|
||||||
|
|||||||
@ -1126,8 +1126,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
|||||||
case .storyArchive:
|
case .storyArchive:
|
||||||
title = presentationData.strings.PeerInfo_PaneArchivedStories
|
title = presentationData.strings.PeerInfo_PaneArchivedStories
|
||||||
case .botPreview:
|
case .botPreview:
|
||||||
//TODO:localize
|
title = presentationData.strings.PeerInfo_PaneBotPreviews
|
||||||
title = "Preview"
|
|
||||||
case .media:
|
case .media:
|
||||||
title = presentationData.strings.PeerInfo_PaneMedia
|
title = presentationData.strings.PeerInfo_PaneMedia
|
||||||
case .files:
|
case .files:
|
||||||
|
|||||||
@ -1357,12 +1357,20 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
} else if hasAbout || hasWebApp {
|
} else if hasAbout || hasWebApp {
|
||||||
var actionButton: PeerInfoScreenLabeledValueItem.Button?
|
var actionButton: PeerInfoScreenLabeledValueItem.Button?
|
||||||
if hasWebApp {
|
if hasWebApp {
|
||||||
//TODO:localize
|
actionButton = PeerInfoScreenLabeledValueItem.Button(title: presentationData.strings.PeerInfo_OpenAppButton, action: {
|
||||||
actionButton = PeerInfoScreenLabeledValueItem.Button(title: "Open App", action: {
|
|
||||||
guard let parentController = interaction.getController() else {
|
guard let parentController = interaction.getController() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let navigationController = parentController.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer {
|
||||||
|
for controller in minimizedContainer.controllers {
|
||||||
|
if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == user.id && mainController.source == .generic {
|
||||||
|
navigationController.maximizeViewController(controller, animated: true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
context.sharedContext.openWebApp(
|
context.sharedContext.openWebApp(
|
||||||
context: context,
|
context: context,
|
||||||
parentController: parentController,
|
parentController: parentController,
|
||||||
@ -1390,8 +1398,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
||||||
//TODO:localize
|
items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: 800, text: presentationData.strings.PeerInfo_AppFooterAdmin, linkAction: { action in
|
||||||
items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: 800, text: "By publishing this mini app, you agree to the [Telegram Terms of Service for Developers](https://telegram.org/privacy).", linkAction: { action in
|
|
||||||
if case let .tap(url) = action {
|
if case let .tap(url) = action {
|
||||||
context.sharedContext.applicationBindings.openUrl(url)
|
context.sharedContext.applicationBindings.openUrl(url)
|
||||||
}
|
}
|
||||||
@ -1399,8 +1406,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
|
|
||||||
currentPeerInfoSection = .peerInfoTrailing
|
currentPeerInfoSection = .peerInfoTrailing
|
||||||
} else if actionButton != nil {
|
} else if actionButton != nil {
|
||||||
//TODO:localize
|
items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: 800, text: presentationData.strings.PeerInfo_AppFooter, linkAction: { action in
|
||||||
items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: 800, text: "By launching this mini app, you agree to the [Terms of Service for Mini Apps](https://telegram.org/privacy).", linkAction: { action in
|
|
||||||
if case let .tap(url) = action {
|
if case let .tap(url) = action {
|
||||||
context.sharedContext.applicationBindings.openUrl(url)
|
context.sharedContext.applicationBindings.openUrl(url)
|
||||||
}
|
}
|
||||||
@ -10894,14 +10900,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
let strings = self.presentationData.strings
|
let strings = self.presentationData.strings
|
||||||
let _ = strings
|
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
|
|
||||||
var ignoreNextActions = false
|
var ignoreNextActions = false
|
||||||
|
|
||||||
if pane.canAddMoreBotPreviews() {
|
if pane.canAddMoreBotPreviews() {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Add Preview", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: strings.BotPreviews_MenuAddPreview, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
if ignoreNextActions {
|
if ignoreNextActions {
|
||||||
@ -10947,8 +10950,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
})))
|
})))
|
||||||
|
|
||||||
if let language = pane.currentBotPreviewLanguage {
|
if let language = pane.currentBotPreviewLanguage {
|
||||||
//TODO:localize
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuDeleteLanguage(language.name).string, textColor: .destructive, icon: { theme in
|
||||||
items.append(.action(ContextMenuActionItem(text: "Delete \(language.name)", textColor: .destructive, icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||||
}, action: { [weak pane] _, a in
|
}, action: { [weak pane] _, a in
|
||||||
if ignoreNextActions {
|
if ignoreNextActions {
|
||||||
@ -14029,192 +14031,3 @@ private final class HeaderContextReferenceContentSource: ContextReferenceContent
|
|||||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
|
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*private func openWebApp(parentController: ViewController, context: AccountContext, peer: EnginePeer, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource) {
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
|
||||||
|
|
||||||
let botName: String
|
|
||||||
let botAddress: String
|
|
||||||
let botVerified: Bool
|
|
||||||
if case let .inline(bot) = source {
|
|
||||||
botName = bot.compactDisplayTitle
|
|
||||||
botAddress = bot.addressName ?? ""
|
|
||||||
botVerified = bot.isVerified
|
|
||||||
} else {
|
|
||||||
botName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
|
||||||
botAddress = peer.addressName ?? ""
|
|
||||||
botVerified = peer.isVerified
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = botAddress
|
|
||||||
|
|
||||||
let openWebView = { [weak parentController] in
|
|
||||||
guard let parentController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if source == .menu {
|
|
||||||
if let navigationController = parentController.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer {
|
|
||||||
for controller in minimizedContainer.controllers {
|
|
||||||
if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == peer.id && mainController.source == .menu {
|
|
||||||
navigationController.maximizeViewController(controller, animated: true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fullSize = false
|
|
||||||
if isTelegramMeLink(url), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, url: url), case .peer(_, .appStart) = internalUrl {
|
|
||||||
fullSize = !url.contains("?mode=compact")
|
|
||||||
}
|
|
||||||
|
|
||||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
|
||||||
let params = WebAppParameters(source: .menu, peerId: peer.id, botId: peer.id, botName: botName, botVerified: botVerified, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: fullSize)
|
|
||||||
//TODO:localize
|
|
||||||
//updatedPresentationData
|
|
||||||
let controller = standaloneWebAppController(context: context, updatedPresentationData: nil, params: params, threadId: nil, openUrl: { [weak parentController] url, concealed, commit in
|
|
||||||
guard let parentController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let _ = parentController
|
|
||||||
/*ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, present: { c, a in
|
|
||||||
presentImpl?(c, a)
|
|
||||||
}, commit: commit)*/
|
|
||||||
}, requestSwitchInline: { [weak parentController] query, chatTypes, completion in
|
|
||||||
guard let parentController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let _ = parentController
|
|
||||||
//ChatControllerImpl.botRequestSwitchInline(context: context, controller: self, peerId: peerId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
|
|
||||||
}, getInputContainerNode: {
|
|
||||||
return nil
|
|
||||||
}, completion: {
|
|
||||||
}, willDismiss: {
|
|
||||||
}, didDismiss: {
|
|
||||||
}, getNavigationController: { [weak parentController] () -> NavigationController? in
|
|
||||||
guard let parentController else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parentController.navigationController as? NavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController
|
|
||||||
})
|
|
||||||
controller.navigationPresentation = .flatModal
|
|
||||||
parentController.push(controller)
|
|
||||||
|
|
||||||
presentImpl = { [weak controller] c, a in
|
|
||||||
controller?.present(c, in: .window(.root), with: a)
|
|
||||||
}
|
|
||||||
let _ = presentImpl
|
|
||||||
} else if simple {
|
|
||||||
var isInline = false
|
|
||||||
var botId = peer.id
|
|
||||||
var botName = botName
|
|
||||||
var botAddress = ""
|
|
||||||
var botVerified = false
|
|
||||||
if case let .inline(bot) = source {
|
|
||||||
isInline = true
|
|
||||||
botId = bot.id
|
|
||||||
botName = bot.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
|
||||||
botAddress = bot.addressName ?? ""
|
|
||||||
botVerified = bot.isVerified
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = botAddress
|
|
||||||
|
|
||||||
let _ = ((context.engine.messages.requestSimpleWebView(botId: botId, url: url, source: isInline ? .inline : .generic, themeParams: generateWebAppThemeParams(presentationData.theme)))
|
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] result in
|
|
||||||
guard let parentController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
|
||||||
let params = WebAppParameters(source: isInline ? .inline : .simple, peerId: peer.id, botId: botId, botName: botName, botVerified: botVerified, url: result.url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize))
|
|
||||||
let controller = standaloneWebAppController(context: context, updatedPresentationData: nil, params: params, threadId: nil, openUrl: { [weak parentController] url, concealed, commit in
|
|
||||||
guard let parentController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let _ = parentController
|
|
||||||
/*ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, present: { c, a in
|
|
||||||
presentImpl?(c, a)
|
|
||||||
}, commit: commit)*/
|
|
||||||
}, requestSwitchInline: { query, chatTypes, completion in
|
|
||||||
//ChatControllerImpl.botRequestSwitchInline(context: context, controller: self, peerId: peerId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
|
|
||||||
}, getNavigationController: { [weak parentController] in
|
|
||||||
guard let parentController else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parentController.navigationController as? NavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController
|
|
||||||
})
|
|
||||||
controller.navigationPresentation = .flatModal
|
|
||||||
parentController.push(controller)
|
|
||||||
|
|
||||||
presentImpl = { [weak controller] c, a in
|
|
||||||
controller?.present(c, in: .window(.root), with: a)
|
|
||||||
}
|
|
||||||
let _ = presentImpl
|
|
||||||
}, error: { [weak parentController] error in
|
|
||||||
guard let parentController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parentController.present(textAlertController(context: context, updatedPresentationData: nil, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
|
||||||
})]), in: .window(.root))
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
let _ = ((context.engine.messages.requestWebView(peerId: peer.id, botId: peer.id, url: !url.isEmpty ? url : nil, payload: nil, themeParams: generateWebAppThemeParams(presentationData.theme), fromMenu: false, replyToMessageId: nil, threadId: nil))
|
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] result in
|
|
||||||
guard let parentController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
|
||||||
let context = context
|
|
||||||
let params = WebAppParameters(source: .button, peerId: peer.id, botId: peer.id, botName: botName, botVerified: botVerified, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize))
|
|
||||||
let controller = standaloneWebAppController(context: context, updatedPresentationData: nil, params: params, threadId: nil, openUrl: { [weak parentController] url, concealed, commit in
|
|
||||||
guard let parentController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let _ = parentController
|
|
||||||
/*ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, present: { c, a in
|
|
||||||
presentImpl?(c, a)
|
|
||||||
}, commit: commit)*/
|
|
||||||
}, completion: {
|
|
||||||
}, getNavigationController: { [weak parentController] in
|
|
||||||
guard let parentController else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parentController.navigationController as? NavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController
|
|
||||||
})
|
|
||||||
controller.navigationPresentation = .flatModal
|
|
||||||
parentController.push(controller)
|
|
||||||
|
|
||||||
presentImpl = { [weak controller] c, a in
|
|
||||||
controller?.present(c, in: .window(.root), with: a)
|
|
||||||
}
|
|
||||||
let _ = presentImpl
|
|
||||||
}, error: { [weak parentController] error in
|
|
||||||
guard let parentController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parentController.present(textAlertController(context: context, updatedPresentationData: nil, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
|
||||||
})]), in: .window(.root))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var botPeer = peer
|
|
||||||
if case let .inline(bot) = source {
|
|
||||||
botPeer = bot
|
|
||||||
}
|
|
||||||
let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id)
|
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] value in
|
|
||||||
guard let parentController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if value {
|
|
||||||
openWebView()
|
|
||||||
} else {
|
|
||||||
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: nil, peer: botPeer, completion: { _ in
|
|
||||||
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
|
|
||||||
openWebView()
|
|
||||||
}, showMore: nil)
|
|
||||||
parentController.present(controller, in: .window(.root))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}*/
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ swift_library(
|
|||||||
"//submodules/SaveToCameraRoll",
|
"//submodules/SaveToCameraRoll",
|
||||||
"//submodules/ShareController",
|
"//submodules/ShareController",
|
||||||
"//submodules/OpenInExternalAppUI",
|
"//submodules/OpenInExternalAppUI",
|
||||||
|
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import ChatTitleView
|
|||||||
import BottomButtonPanelComponent
|
import BottomButtonPanelComponent
|
||||||
import UndoUI
|
import UndoUI
|
||||||
import MoreHeaderButton
|
import MoreHeaderButton
|
||||||
import MediaEditorScreen
|
import SaveProgressScreen
|
||||||
import SaveToCameraRoll
|
import SaveToCameraRoll
|
||||||
|
|
||||||
final class PeerInfoStoryGridScreenComponent: Component {
|
final class PeerInfoStoryGridScreenComponent: Component {
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import ChatTitleView
|
|||||||
import BottomButtonPanelComponent
|
import BottomButtonPanelComponent
|
||||||
import UndoUI
|
import UndoUI
|
||||||
import MoreHeaderButton
|
import MoreHeaderButton
|
||||||
import MediaEditorScreen
|
|
||||||
import SaveToCameraRoll
|
import SaveToCameraRoll
|
||||||
import ShareController
|
import ShareController
|
||||||
import OpenInExternalAppUI
|
import OpenInExternalAppUI
|
||||||
|
|||||||
@ -1629,6 +1629,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
public private(set) var isSelectionModeActive: Bool
|
public private(set) var isSelectionModeActive: Bool
|
||||||
|
|
||||||
private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)?
|
private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)?
|
||||||
|
private var listBottomInset: CGFloat?
|
||||||
|
|
||||||
private let ready = Promise<Bool>()
|
private let ready = Promise<Bool>()
|
||||||
private var didSetReady: Bool = false
|
private var didSetReady: Bool = false
|
||||||
@ -2053,10 +2054,17 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
return SparseItemGrid.ShimmerColors(background: 0xffffff, foreground: 0xffffff)
|
return SparseItemGrid.ShimmerColors(background: 0xffffff, foreground: 0xffffff)
|
||||||
}
|
}
|
||||||
|
|
||||||
let backgroundColor = presentationData.theme.list.mediaPlaceholderColor
|
if case .botPreview = scope {
|
||||||
let foregroundColor = presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.6)
|
let backgroundColor = presentationData.theme.list.plainBackgroundColor
|
||||||
|
let foregroundColor = presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.6)
|
||||||
return SparseItemGrid.ShimmerColors(background: backgroundColor.argb, foreground: foregroundColor.argb)
|
|
||||||
|
return SparseItemGrid.ShimmerColors(background: backgroundColor.argb, foreground: foregroundColor.argb)
|
||||||
|
} else {
|
||||||
|
let backgroundColor = presentationData.theme.list.mediaPlaceholderColor
|
||||||
|
let foregroundColor = presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.6)
|
||||||
|
|
||||||
|
return SparseItemGrid.ShimmerColors(background: backgroundColor.argb, foreground: foregroundColor.argb)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.itemGridBinding.updateShimmerLayersImpl = { [weak self] layer in
|
self.itemGridBinding.updateShimmerLayersImpl = { [weak self] layer in
|
||||||
@ -2496,8 +2504,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: isPinned ? "anim_toastunpin" : "anim_toastpin", scale: 0.06, colors: [:], title: toastTitle, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: isPinned ? "anim_toastunpin" : "anim_toastpin", scale: 0.06, colors: [:], title: toastTitle, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||||
})))
|
})))
|
||||||
if isPinned && self.canReorder() {
|
if isPinned && self.canReorder() {
|
||||||
//TODO:localize
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||||
items.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
|
||||||
c?.dismiss(completion: {
|
c?.dismiss(completion: {
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -2561,8 +2568,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
if canManage, case .botPreview = self.scope, self.canReorder() {
|
if canManage, case .botPreview = self.scope, self.canReorder() {
|
||||||
//TODO:localize
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||||
items.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
|
||||||
c?.dismiss(completion: {
|
c?.dismiss(completion: {
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -2730,11 +2736,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
let title: String
|
let title: String
|
||||||
if state.totalCount == 0 {
|
if state.totalCount == 0 {
|
||||||
if case .botPreview = self.scope {
|
if case .botPreview = self.scope {
|
||||||
//TODO:localize
|
|
||||||
if state.isLoading {
|
if state.isLoading {
|
||||||
title = "loading"
|
title = self.presentationData.strings.BotPreviews_SubtitleLoading
|
||||||
} else {
|
} else {
|
||||||
title = "no preview added"
|
title = self.presentationData.strings.BotPreviews_SubtitleEmpty
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
title = ""
|
title = ""
|
||||||
@ -2748,12 +2753,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
title = self.presentationData.strings.StoryList_SubtitleCount(Int32(state.totalCount))
|
title = self.presentationData.strings.StoryList_SubtitleCount(Int32(state.totalCount))
|
||||||
}
|
}
|
||||||
} else if case .botPreview = self.scope {
|
} else if case .botPreview = self.scope {
|
||||||
//TODO:localize
|
title = self.presentationData.strings.BotPreviews_SubtitleCount(Int32(state.totalCount))
|
||||||
if state.totalCount == 1 {
|
|
||||||
title = "1 preview"
|
|
||||||
} else {
|
|
||||||
title = "\(state.totalCount) previews"
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
title = ""
|
title = ""
|
||||||
}
|
}
|
||||||
@ -3349,18 +3349,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
let title: String = presentationData.strings.BotPreviews_SheetDeleteTitle(Int32(mappedMedia.count))
|
||||||
let title: String
|
|
||||||
if mappedMedia.count == 1 {
|
|
||||||
title = "Delete 1 Preview?"
|
|
||||||
} else {
|
|
||||||
title = "Delete \(mappedMedia.count) Previews?"
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.setItemGroups([
|
controller.setItemGroups([
|
||||||
ActionSheetItemGroup(items: [
|
ActionSheetItemGroup(items: [
|
||||||
ActionSheetTextItem(title: title),
|
ActionSheetTextItem(title: title),
|
||||||
ActionSheetButtonItem(title: "Delete", color: .destructive, action: { [weak self] in
|
ActionSheetButtonItem(title: presentationData.strings.Common_Delete, color: .destructive, action: { [weak self] in
|
||||||
dismissAction()
|
dismissAction()
|
||||||
|
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -3396,7 +3390,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
}
|
}
|
||||||
if case .botPreview = self.scope, self.canManageStories {
|
if case .botPreview = self.scope, self.canManageStories {
|
||||||
self.updateBotPreviewLanguageTab(size: currentParams.size, topInset: currentParams.topInset, transition: transition)
|
self.updateBotPreviewLanguageTab(size: currentParams.size, topInset: currentParams.topInset, transition: transition)
|
||||||
self.updateBotPreviewFooter(size: currentParams.size, bottomInset: currentParams.bottomInset, transition: transition)
|
self.updateBotPreviewFooter(size: currentParams.size, bottomInset: 0.0, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3564,11 +3558,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
self.botPreviewLanguageTab = botPreviewLanguageTab
|
self.botPreviewLanguageTab = botPreviewLanguageTab
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
var languageItems: [TabSelectorComponent.Item] = []
|
var languageItems: [TabSelectorComponent.Item] = []
|
||||||
languageItems.append(TabSelectorComponent.Item(
|
languageItems.append(TabSelectorComponent.Item(
|
||||||
id: AnyHashable("_main"),
|
id: AnyHashable("_main"),
|
||||||
title: "Main"
|
title: self.presentationData.strings.BotPreviews_LanguageTab_Main
|
||||||
))
|
))
|
||||||
for language in self.currentBotPreviewLanguages {
|
for language in self.currentBotPreviewLanguages {
|
||||||
languageItems.append(TabSelectorComponent.Item(
|
languageItems.append(TabSelectorComponent.Item(
|
||||||
@ -3578,7 +3571,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
}
|
}
|
||||||
languageItems.append(TabSelectorComponent.Item(
|
languageItems.append(TabSelectorComponent.Item(
|
||||||
id: AnyHashable("_add"),
|
id: AnyHashable("_add"),
|
||||||
title: "+ Add Language"
|
title: self.presentationData.strings.BotPreviews_LanguageTab_Add
|
||||||
))
|
))
|
||||||
var selectedLanguageId = "_main"
|
var selectedLanguageId = "_main"
|
||||||
if let listSource = self.listSource as? BotPreviewStoryListContext, let language = listSource.language {
|
if let listSource = self.listSource as? BotPreviewStoryListContext, let language = listSource.language {
|
||||||
@ -3645,9 +3638,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
let text: String
|
let text: String
|
||||||
if let listSource = self.listSource as? BotPreviewStoryListContext, let id = listSource.language, let language = self.currentBotPreviewLanguages.first(where: { $0.id == id }) {
|
if let listSource = self.listSource as? BotPreviewStoryListContext, let id = listSource.language, let language = self.currentBotPreviewLanguages.first(where: { $0.id == id }) {
|
||||||
isMainLanguage = false
|
isMainLanguage = false
|
||||||
text = "This preview will be displayed for all users who have \(language.name) set as their language."
|
|
||||||
|
text = self.presentationData.strings.BotPreviews_TranslationFooter_Text(language.name).string
|
||||||
} else {
|
} else {
|
||||||
text = "This preview will be shown by default. You can also add translations into specific languages."
|
text = self.presentationData.strings.BotPreviews_DefaultFooter_Text
|
||||||
}
|
}
|
||||||
|
|
||||||
let botPreviewFooterSize = botPreviewFooter.update(
|
let botPreviewFooterSize = botPreviewFooter.update(
|
||||||
@ -3659,14 +3653,18 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
animationName: nil,
|
animationName: nil,
|
||||||
title: nil,
|
title: nil,
|
||||||
text: text,
|
text: text,
|
||||||
actionTitle: "Add Preview",
|
actionTitle: self.presentationData.strings.BotPreviews_Empty_Add,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.emptyAction?()
|
if self.canAddMoreBotPreviews() {
|
||||||
|
self.emptyAction?()
|
||||||
|
} else {
|
||||||
|
self.presentUnableToAddMorePreviewsAlert()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
additionalActionTitle: isMainLanguage ? "Create a Translation" : nil,
|
additionalActionTitle: isMainLanguage ? self.presentationData.strings.BotPreviews_Empty_AddTranslation : nil,
|
||||||
additionalAction: { [weak self] in
|
additionalAction: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -3675,12 +3673,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
self.presentAddBotPreviewLanguage()
|
self.presentAddBotPreviewLanguage()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
additionalActionSeparator: isMainLanguage ? "or" : nil
|
additionalActionSeparator: isMainLanguage ? self.presentationData.strings.BotPreviews_Empty_Separator : nil
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: size.width, height: 1000.0)
|
containerSize: CGSize(width: size.width, height: 1000.0)
|
||||||
)
|
)
|
||||||
let botPreviewFooterFrame = CGRect(origin: CGPoint(x: floor((size.width - botPreviewFooterSize.width) * 0.5), y: self.itemGrid.contentBottomOffset - botPreviewFooterSize.height - bottomInset), size: botPreviewFooterSize)
|
let botPreviewFooterFrame = CGRect(origin: CGPoint(x: floor((size.width - botPreviewFooterSize.width) * 0.5), y: self.itemGrid.contentBottomOffset + 16.0), size: botPreviewFooterSize)
|
||||||
if let botPreviewFooterView = botPreviewFooter.view {
|
if let botPreviewFooterView = botPreviewFooter.view {
|
||||||
if botPreviewFooterView.superview == nil {
|
if botPreviewFooterView.superview == nil {
|
||||||
self.view.addSubview(botPreviewFooterView)
|
self.view.addSubview(botPreviewFooterView)
|
||||||
@ -3746,16 +3744,16 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
transition.updateFrame(layer: barBackgroundLayer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: gridTopInset)))
|
transition.updateFrame(layer: barBackgroundLayer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: gridTopInset)))
|
||||||
}
|
}
|
||||||
|
|
||||||
let defaultBottomInset = bottomInset
|
var listBottomInset = bottomInset
|
||||||
var bottomInset = bottomInset
|
var bottomInset = bottomInset
|
||||||
|
|
||||||
if case .botPreview = self.scope, self.canManageStories {
|
if case .botPreview = self.scope, self.canManageStories {
|
||||||
updateBotPreviewLanguageTab(size: size, topInset: topInset, transition: transition)
|
updateBotPreviewLanguageTab(size: size, topInset: topInset, transition: transition)
|
||||||
gridTopInset += 50.0
|
gridTopInset += 50.0
|
||||||
|
|
||||||
updateBotPreviewFooter(size: size, bottomInset: defaultBottomInset, transition: transition)
|
updateBotPreviewFooter(size: size, bottomInset: 0.0, transition: transition)
|
||||||
if let botPreviewFooterView = self.botPreviewFooter?.view {
|
if let botPreviewFooterView = self.botPreviewFooter?.view {
|
||||||
bottomInset += 18.0 + botPreviewFooterView.bounds.height
|
listBottomInset += 18.0 + botPreviewFooterView.bounds.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3886,6 +3884,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
selectionPanelTransition.setFrame(view: selectionPanelView, frame: selectionPanelFrame)
|
selectionPanelTransition.setFrame(view: selectionPanelView, frame: selectionPanelFrame)
|
||||||
}
|
}
|
||||||
bottomInset = selectionPanelSize.height
|
bottomInset = selectionPanelSize.height
|
||||||
|
listBottomInset += selectionPanelSize.height
|
||||||
} else if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories, case .botPreview = self.scope {
|
} else if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories, case .botPreview = self.scope {
|
||||||
let selectionPanel: ComponentView<Empty>
|
let selectionPanel: ComponentView<Empty>
|
||||||
var selectionPanelTransition = ComponentTransition(transition)
|
var selectionPanelTransition = ComponentTransition(transition)
|
||||||
@ -3932,6 +3931,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
selectionPanelTransition.setFrame(view: selectionPanelView, frame: selectionPanelFrame)
|
selectionPanelTransition.setFrame(view: selectionPanelView, frame: selectionPanelFrame)
|
||||||
}
|
}
|
||||||
bottomInset = selectionPanelSize.height
|
bottomInset = selectionPanelSize.height
|
||||||
|
listBottomInset += selectionPanelSize.height
|
||||||
} else if let selectionPanel = self.selectionPanel {
|
} else if let selectionPanel = self.selectionPanel {
|
||||||
self.selectionPanel = nil
|
self.selectionPanel = nil
|
||||||
if let selectionPanelView = selectionPanel.view {
|
if let selectionPanelView = selectionPanel.view {
|
||||||
@ -4018,7 +4018,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
emptyStateView = ComponentView()
|
emptyStateView = ComponentView()
|
||||||
self.emptyStateView = emptyStateView
|
self.emptyStateView = emptyStateView
|
||||||
}
|
}
|
||||||
//TODO:localize
|
|
||||||
|
|
||||||
var isMainLanguage = true
|
var isMainLanguage = true
|
||||||
if let listSource = self.listSource as? BotPreviewStoryListContext, let _ = listSource.language {
|
if let listSource = self.listSource as? BotPreviewStoryListContext, let _ = listSource.language {
|
||||||
@ -4032,16 +4031,20 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
theme: presentationData.theme,
|
theme: presentationData.theme,
|
||||||
fitToHeight: self.isProfileEmbedded,
|
fitToHeight: self.isProfileEmbedded,
|
||||||
animationName: nil,
|
animationName: nil,
|
||||||
title: "No Preview",
|
title: presentationData.strings.BotPreviews_Empty_Title,
|
||||||
text: "Upload up to \(self.maxBotPreviewCount) screenshots and video demos for your mini app.",
|
text: presentationData.strings.BotPreviews_Empty_Text(Int32(self.maxBotPreviewCount)),
|
||||||
actionTitle: self.canManageStories ? "Add Preview" : nil,
|
actionTitle: self.canManageStories ? presentationData.strings.BotPreviews_Empty_Add : nil,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.emptyAction?()
|
if self.canAddMoreBotPreviews() {
|
||||||
|
self.emptyAction?()
|
||||||
|
} else {
|
||||||
|
self.presentUnableToAddMorePreviewsAlert()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
additionalActionTitle: self.canManageStories ? (isMainLanguage ? "Create a Translation" : "Delete this Translation") : nil,
|
additionalActionTitle: self.canManageStories ? (isMainLanguage ? presentationData.strings.BotPreviews_Empty_AddTranslation : presentationData.strings.BotPreviews_Empty_DeleteTranslation) : nil,
|
||||||
additionalAction: {
|
additionalAction: {
|
||||||
if isMainLanguage {
|
if isMainLanguage {
|
||||||
self.presentAddBotPreviewLanguage()
|
self.presentAddBotPreviewLanguage()
|
||||||
@ -4049,7 +4052,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
self.presentDeleteBotPreviewLanguage()
|
self.presentDeleteBotPreviewLanguage()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
additionalActionSeparator: self.canManageStories ? "or" : nil
|
additionalActionSeparator: self.canManageStories ? presentationData.strings.BotPreviews_Empty_Separator : nil
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: size.width, height: size.height - gridTopInset - bottomInset)
|
containerSize: CGSize(width: size.width, height: size.height - gridTopInset - bottomInset)
|
||||||
@ -4133,11 +4136,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.itemGrid.pinchEnabled = items.count > 2 && !self.isReordering
|
self.itemGrid.pinchEnabled = items.count > 2 && !self.isReordering
|
||||||
self.itemGrid.update(size: size, insets: UIEdgeInsets(top: gridTopInset, left: sideInset, bottom: bottomInset, right: sideInset), useSideInsets: !isList, scrollIndicatorInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), lockScrollingAtTop: isScrollingLockedAtTop, fixedItemHeight: fixedItemHeight, fixedItemAspect: fixedItemAspect, adjustForSmallCount: adjustForSmallCount, items: items, theme: self.itemGridBinding.chatPresentationData.theme.theme, synchronous: wasFirstTime ? .full : .none, transition: animateGridItems ? .spring(duration: 0.35) : .immediate)
|
self.itemGrid.update(size: size, insets: UIEdgeInsets(top: gridTopInset, left: sideInset, bottom: listBottomInset, right: sideInset), useSideInsets: !isList, scrollIndicatorInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), lockScrollingAtTop: isScrollingLockedAtTop, fixedItemHeight: fixedItemHeight, fixedItemAspect: fixedItemAspect, adjustForSmallCount: adjustForSmallCount, items: items, theme: self.itemGridBinding.chatPresentationData.theme.theme, synchronous: wasFirstTime ? .full : .none, transition: animateGridItems ? .spring(duration: 0.35) : .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.listBottomInset = listBottomInset
|
||||||
if case .botPreview = self.scope, self.canManageStories {
|
if case .botPreview = self.scope, self.canManageStories {
|
||||||
updateBotPreviewFooter(size: size, bottomInset: defaultBottomInset, transition: transition)
|
updateBotPreviewFooter(size: size, bottomInset: 0.0, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4238,7 +4242,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentAddBotPreviewLanguage() {
|
private func presentAddBotPreviewLanguage() {
|
||||||
self.parentController?.push(LanguageSelectionScreen(context: self.context, selectLocalization: { [weak self] info in
|
let excludeIds: [String] = self.currentBotPreviewLanguages.map(\.id)
|
||||||
|
self.parentController?.push(LanguageSelectionScreen(context: self.context, excludeIds: excludeIds, selectLocalization: { [weak self] info in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -4246,12 +4251,18 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func presentUnableToAddMorePreviewsAlert() {
|
||||||
|
self.parentController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.BotPreviews_AlertTooManyPreviews(Int32(self.maxBotPreviewCount)), actions: [
|
||||||
|
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
|
||||||
|
})
|
||||||
|
], parseMarkdown: true), in: .window(.root))
|
||||||
|
}
|
||||||
|
|
||||||
public func presentDeleteBotPreviewLanguage() {
|
public func presentDeleteBotPreviewLanguage() {
|
||||||
//TODO:localize
|
self.parentController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: self.presentationData.strings.BotPreviews_DeleteTranslationAlert_Title, text: self.presentationData.strings.BotPreviews_DeleteTranslationAlert_Text, actions: [
|
||||||
self.parentController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: "Delete Translation", text: "Are you sure you want to delete this translation?", actions: [
|
|
||||||
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {
|
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {
|
||||||
}),
|
}),
|
||||||
TextAlertAction(type: .destructiveAction, title: "OK", action: { [weak self] in
|
TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.Common_OK, action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
24
submodules/TelegramUI/Components/SaveProgressScreen/BUILD
Normal file
24
submodules/TelegramUI/Components/SaveProgressScreen/BUILD
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "SaveProgressScreen",
|
||||||
|
module_name = "SaveProgressScreen",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/Components/ViewControllerComponent",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
|
"//submodules/Components/LottieAnimationComponent",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -11,6 +11,7 @@ import SearchUI
|
|||||||
|
|
||||||
public class LanguageSelectionScreen: ViewController {
|
public class LanguageSelectionScreen: ViewController {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
private let excludeIds: [String]
|
||||||
private let selectLocalization: (LocalizationInfo) -> Void
|
private let selectLocalization: (LocalizationInfo) -> Void
|
||||||
|
|
||||||
private var controllerNode: LanguageSelectionScreenNode {
|
private var controllerNode: LanguageSelectionScreenNode {
|
||||||
@ -29,8 +30,9 @@ public class LanguageSelectionScreen: ViewController {
|
|||||||
|
|
||||||
private var previousContentOffset: ListViewVisibleContentOffset?
|
private var previousContentOffset: ListViewVisibleContentOffset?
|
||||||
|
|
||||||
public init(context: AccountContext, selectLocalization: @escaping (LocalizationInfo) -> Void) {
|
public init(context: AccountContext, excludeIds: [String] = [], selectLocalization: @escaping (LocalizationInfo) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.excludeIds = excludeIds
|
||||||
self.selectLocalization = selectLocalization
|
self.selectLocalization = selectLocalization
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@ -40,8 +42,7 @@ public class LanguageSelectionScreen: ViewController {
|
|||||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||||
self.navigationPresentation = .modal
|
self.navigationPresentation = .modal
|
||||||
|
|
||||||
//TODO:localize
|
self.title = self.presentationData.strings.BotPreviews_SelectLanguage_Title
|
||||||
self.title = "Add a Translation"
|
|
||||||
|
|
||||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ public class LanguageSelectionScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = LanguageSelectionScreenNode(context: self.context, presentationData: self.presentationData, navigationBar: self.navigationBar!, requestActivateSearch: { [weak self] in
|
self.displayNode = LanguageSelectionScreenNode(context: self.context, presentationData: self.presentationData, navigationBar: self.navigationBar!, excludeIds: self.excludeIds, requestActivateSearch: { [weak self] in
|
||||||
self?.activateSearch()
|
self?.activateSearch()
|
||||||
}, requestDeactivateSearch: { [weak self] in
|
}, requestDeactivateSearch: { [weak self] in
|
||||||
self?.deactivateSearch()
|
self?.deactivateSearch()
|
||||||
|
|||||||
@ -294,6 +294,7 @@ final class LanguageSelectionScreenNode: ViewControllerTracingNode {
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private weak var navigationBar: NavigationBar?
|
private weak var navigationBar: NavigationBar?
|
||||||
|
private let excludeIds: [String]
|
||||||
private let requestActivateSearch: () -> Void
|
private let requestActivateSearch: () -> Void
|
||||||
private let requestDeactivateSearch: () -> Void
|
private let requestDeactivateSearch: () -> Void
|
||||||
private let present: (ViewController, Any?) -> Void
|
private let present: (ViewController, Any?) -> Void
|
||||||
@ -316,11 +317,12 @@ final class LanguageSelectionScreenNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
private var currentListState: LocalizationListState?
|
private var currentListState: LocalizationListState?
|
||||||
|
|
||||||
init(context: AccountContext, presentationData: PresentationData, navigationBar: NavigationBar, requestActivateSearch: @escaping () -> Void, requestDeactivateSearch: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, push: @escaping (ViewController) -> Void, selectLocalization: @escaping (LocalizationInfo) -> Void) {
|
init(context: AccountContext, presentationData: PresentationData, navigationBar: NavigationBar, excludeIds: [String], requestActivateSearch: @escaping () -> Void, requestDeactivateSearch: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, push: @escaping (ViewController) -> Void, selectLocalization: @escaping (LocalizationInfo) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.presentationDataValue.set(.single(presentationData))
|
self.presentationDataValue.set(.single(presentationData))
|
||||||
self.navigationBar = navigationBar
|
self.navigationBar = navigationBar
|
||||||
|
self.excludeIds = excludeIds
|
||||||
self.requestActivateSearch = requestActivateSearch
|
self.requestActivateSearch = requestActivateSearch
|
||||||
self.requestDeactivateSearch = requestDeactivateSearch
|
self.requestDeactivateSearch = requestDeactivateSearch
|
||||||
self.present = present
|
self.present = present
|
||||||
@ -362,6 +364,12 @@ final class LanguageSelectionScreenNode: ViewControllerTracingNode {
|
|||||||
var entries: [LanguageListEntry] = []
|
var entries: [LanguageListEntry] = []
|
||||||
var existingIds = Set<String>()
|
var existingIds = Set<String>()
|
||||||
|
|
||||||
|
var localizationListState = localizationListState
|
||||||
|
localizationListState.availableOfficialLocalizations = localizationListState.availableOfficialLocalizations.filter {
|
||||||
|
!strongSelf.excludeIds.contains($0.languageCode)
|
||||||
|
}
|
||||||
|
localizationListState.availableSavedLocalizations = []
|
||||||
|
|
||||||
if !localizationListState.availableOfficialLocalizations.isEmpty {
|
if !localizationListState.availableOfficialLocalizations.isEmpty {
|
||||||
strongSelf.currentListState = localizationListState
|
strongSelf.currentListState = localizationListState
|
||||||
|
|
||||||
|
|||||||
@ -976,6 +976,7 @@ open class SpaceWarpView4: UIView, SpaceWarpView {
|
|||||||
meshView.frame = CGRect(origin: CGPoint(), size: size)
|
meshView.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
let pixelStep = CGPoint()
|
let pixelStep = CGPoint()
|
||||||
|
//let pixelStep = CGPoint(x: CGFloat(resolution.x) * 0.33, y: CGFloat(resolution.y) * 0.33)
|
||||||
let itemSize = CGSize(width: size.width / CGFloat(resolution.x), height: size.height / CGFloat(resolution.y))
|
let itemSize = CGSize(width: size.width / CGFloat(resolution.x), height: size.height / CGFloat(resolution.y))
|
||||||
|
|
||||||
let params = RippleParams(amplitude: 26.0, frequency: 15.0, decay: 8.0, speed: 1400.0)
|
let params = RippleParams(amplitude: 26.0, frequency: 15.0, decay: 8.0, speed: 1400.0)
|
||||||
|
|||||||
@ -98,6 +98,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen",
|
"//submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen",
|
||||||
"//submodules/TelegramUI/Components/SliderContextItem",
|
"//submodules/TelegramUI/Components/SliderContextItem",
|
||||||
"//submodules/TelegramUI/Components/InteractiveTextComponent",
|
"//submodules/TelegramUI/Components/InteractiveTextComponent",
|
||||||
|
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import TelegramUIPreferences
|
|||||||
import StoryFooterPanelComponent
|
import StoryFooterPanelComponent
|
||||||
import TelegramNotices
|
import TelegramNotices
|
||||||
import SliderContextItem
|
import SliderContextItem
|
||||||
|
import SaveProgressScreen
|
||||||
|
|
||||||
public final class StoryAvailableReactions: Equatable {
|
public final class StoryAvailableReactions: Equatable {
|
||||||
let reactionItems: [ReactionItem]
|
let reactionItems: [ReactionItem]
|
||||||
@ -5655,8 +5656,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
let deleteTitle: String
|
let deleteTitle: String
|
||||||
if case let .user(user) = component.slice.peer, user.botInfo != nil {
|
if case let .user(user) = component.slice.peer, user.botInfo != nil {
|
||||||
//TODO:localize
|
deleteTitle = component.strings.BotPreview_ViewContextDelete
|
||||||
deleteTitle = "Delete Preview"
|
|
||||||
} else {
|
} else {
|
||||||
deleteTitle = component.strings.Story_ContextDeleteStory
|
deleteTitle = component.strings.Story_ContextDeleteStory
|
||||||
}
|
}
|
||||||
@ -6604,8 +6604,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
if case let .user(user) = component.slice.peer, let botInfo = user.botInfo {
|
if case let .user(user) = component.slice.peer, let botInfo = user.botInfo {
|
||||||
if botInfo.flags.contains(.canEdit) {
|
if botInfo.flags.contains(.canEdit) {
|
||||||
//TODO:localize
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.BotPreviews_MenuReorder, icon: { theme in
|
||||||
items.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -6616,8 +6615,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
component.reorder()
|
component.reorder()
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: "Delete", textColor: .destructive, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Delete, textColor: .destructive, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.destructiveColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,10 @@ var uiWebview_SearchResultCount = 0;
|
|||||||
keyword - string to search
|
keyword - string to search
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
function isElementVisible(e) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
function uiWebview_HighlightAllOccurencesOfStringForElement(element,keyword) {
|
function uiWebview_HighlightAllOccurencesOfStringForElement(element,keyword) {
|
||||||
if (element) {
|
if (element) {
|
||||||
if (element.nodeType == 3) { // Text node
|
if (element.nodeType == 3) { // Text node
|
||||||
@ -86,7 +90,7 @@ function uiWebview_HighlightAllOccurencesOfStringForElement(element,keyword) {
|
|||||||
|
|
||||||
|
|
||||||
} else if (element.nodeType == 1) { // Element node
|
} else if (element.nodeType == 1) { // Element node
|
||||||
if (element.style.display != "none" && element.nodeName.toLowerCase() != 'select') {
|
if (element.nodeName.toLowerCase() != 'select' && isElementVisible(element)) {
|
||||||
for (var i=element.childNodes.length-1; i>=0; i--) {
|
for (var i=element.childNodes.length-1; i>=0; i--) {
|
||||||
uiWebview_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword);
|
uiWebview_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1008,11 +1008,12 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let urlScheme = (parsedUrl.scheme ?? "").lowercased()
|
||||||
var isInternetUrl = false
|
var isInternetUrl = false
|
||||||
if parsedUrl.scheme == "http" || parsedUrl.scheme == "https" {
|
if ["http", "https"].contains(urlScheme) {
|
||||||
isInternetUrl = true
|
isInternetUrl = true
|
||||||
}
|
}
|
||||||
if parsedUrl.scheme == "tonsite" {
|
if urlScheme == "tonsite" {
|
||||||
isInternetUrl = true
|
isInternetUrl = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1032,7 +1033,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
settings = .defaultSettings
|
settings = .defaultSettings
|
||||||
}
|
}
|
||||||
if accessChallengeData.data.isLockable {
|
if accessChallengeData.data.isLockable {
|
||||||
if passcodeSettings.autolockTimeout != nil && settings.defaultWebBrowser == nil {
|
if passcodeSettings.autolockTimeout != nil && settings.defaultWebBrowser == "inApp" {
|
||||||
settings = WebBrowserSettings(defaultWebBrowser: "safari", exceptions: [])
|
settings = WebBrowserSettings(defaultWebBrowser: "safari", exceptions: [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -437,7 +437,6 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.textNode.visibility = true
|
self.textNode.visibility = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||||
|
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
|
|||||||
@ -1203,7 +1203,11 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String
|
|||||||
var url = url
|
var url = url
|
||||||
if !url.contains("://") && !url.hasPrefix("tel:") && !url.hasPrefix("mailto:") && !url.hasPrefix("calshow:") {
|
if !url.contains("://") && !url.hasPrefix("tel:") && !url.hasPrefix("mailto:") && !url.hasPrefix("calshow:") {
|
||||||
if !(url.hasPrefix("http") || url.hasPrefix("https")) {
|
if !(url.hasPrefix("http") || url.hasPrefix("https")) {
|
||||||
url = "http://\(url)"
|
if let mappedURL = URL(string: "https://\(url)"), let host = mappedURL.host, host.lowercased().hasSuffix(".ton") {
|
||||||
|
url = "tonsite://\(url)"
|
||||||
|
} else {
|
||||||
|
url = "http://\(url)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user