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