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

This commit is contained in:
Mikhail Filimonov 2024-07-25 14:43:32 -03:00
commit 2179f76fbe
41 changed files with 1086 additions and 408 deletions

View File

@ -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";

View File

@ -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",

View File

@ -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)))

View File

@ -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: {},

View File

@ -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)?

View File

@ -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?
} }

View File

@ -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)

View File

@ -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 {

View File

@ -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
} }
} }

View File

@ -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)

View File

@ -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

View File

@ -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
} }
} }
} }

View 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
}
}

View File

@ -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

View File

@ -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

View File

@ -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] {

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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",

View File

@ -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()
}
}) })
} }

View File

@ -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()

View File

@ -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)

View File

@ -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:

View File

@ -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))
}
})
}*/

View File

@ -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",

View File

@ -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 {

View File

@ -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

View File

@ -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
} }

View 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",
],
)

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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",

View File

@ -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)

View File

@ -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);
} }

View File

@ -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: [])
} }
} }

View File

@ -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

View File

@ -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)"
}
} }
} }