Various improvements

This commit is contained in:
Ilya Laktyushin
2024-07-23 22:01:56 +04:00
parent 31ba87fb0f
commit 1c7834ad57
43 changed files with 3867 additions and 459 deletions

View File

@@ -73,13 +73,14 @@ private final class BrowserScreenComponent: CombinedComponent {
static var body: Body {
let navigationBar = Child(BrowserNavigationBarComponent.self)
let toolbar = Child(BrowserToolbarComponent.self)
let addressList = Child(BrowserAddressListComponent.self)
return { context in
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
let performAction = context.component.performAction
let performHoldAction = context.component.performHoldAction
let navigationContent: AnyComponentWithIdentity<Empty>?
let navigationContent: AnyComponentWithIdentity<BrowserNavigationBarEnvironment>?
var navigationLeftItems: [AnyComponentWithIdentity<Empty>]
var navigationRightItems: [AnyComponentWithIdentity<Empty>]
if context.component.presentationState.isSearching {
@@ -96,51 +97,78 @@ private final class BrowserScreenComponent: CombinedComponent {
navigationLeftItems = []
navigationRightItems = []
} else {
let title = context.component.contentState?.title ?? ""
navigationContent = AnyComponentWithIdentity(
id: "title_\(title)",
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: title, font: Font.bold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor, paragraphAlignment: .center)), horizontalAlignment: .center, maximumNumberOfLines: 1)
)
)
navigationLeftItems = [
AnyComponentWithIdentity(
id: "close",
let contentType = context.component.contentState?.contentType ?? .instantPage
switch contentType {
case .webPage:
navigationContent = AnyComponentWithIdentity(
id: "addressBar",
component: AnyComponent(
Button(
content: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.WebBrowser_Done, font: Font.regular(17.0), textColor: environment.theme.rootController.navigationBar.accentTextColor, paragraphAlignment: .center)), horizontalAlignment: .left, maximumNumberOfLines: 1)
),
action: {
performAction.invoke(.close)
}
AddressBarContentComponent(
theme: environment.theme,
strings: environment.strings,
url: context.component.contentState?.url ?? "",
isSecure: context.component.contentState?.isSecure ?? false,
isExpanded: context.component.presentationState.addressFocused,
performAction: performAction
)
)
)
]
navigationRightItems = [
AnyComponentWithIdentity(
id: "settings",
case .instantPage, .document:
let title = context.component.contentState?.title ?? ""
navigationContent = AnyComponentWithIdentity(
id: "titleBar_\(title)",
component: AnyComponent(
ReferenceButtonComponent(
content: AnyComponent(
LottieComponent(
content: LottieComponent.AppBundleContent(
name: "anim_moredots"
),
color: environment.theme.rootController.navigationBar.accentTextColor,
size: CGSize(width: 30.0, height: 30.0)
)
),
tag: settingsTag,
action: {
performAction.invoke(.openSettings)
}
TitleBarContentComponent(
theme: environment.theme,
title: title
)
)
)
]
}
if context.component.presentationState.addressFocused {
navigationLeftItems = []
navigationRightItems = []
} else {
navigationLeftItems = [
AnyComponentWithIdentity(
id: "close",
component: AnyComponent(
Button(
content: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.WebBrowser_Done, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.accentTextColor, paragraphAlignment: .center)), horizontalAlignment: .left, maximumNumberOfLines: 1)
),
action: {
performAction.invoke(.close)
}
)
)
)
]
navigationRightItems = [
AnyComponentWithIdentity(
id: "settings",
component: AnyComponent(
ReferenceButtonComponent(
content: AnyComponent(
LottieComponent(
content: LottieComponent.AppBundleContent(
name: "anim_moredots"
),
color: environment.theme.rootController.navigationBar.accentTextColor,
size: CGSize(width: 30.0, height: 30.0)
)
),
tag: settingsTag,
action: {
performAction.invoke(.openSettings)
}
)
)
)
]
}
}
let collapseFraction = context.component.presentationState.isSearching ? 0.0 : context.component.panelCollapseFraction
@@ -224,6 +252,26 @@ private final class BrowserScreenComponent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0))
)
if context.component.presentationState.addressFocused {
let addressList = addressList.update(
component: BrowserAddressListComponent(
context: context.component.context,
theme: environment.theme,
strings: environment.strings,
navigateTo: { url in
performAction.invoke(.navigateTo(url))
}
),
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height - navigationBar.size.height - toolbar.size.height),
transition: context.transition
)
context.add(addressList
.position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height + addressList.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
}
return context.availableSize
}
}
@@ -239,6 +287,7 @@ struct BrowserPresentationState: Equatable {
var searchResultIndex: Int
var searchResultCount: Int
var searchQueryIsEmpty: Bool
var addressFocused: Bool
}
public class BrowserScreen: ViewController, MinimizableController {
@@ -262,6 +311,9 @@ public class BrowserScreen: ViewController, MinimizableController {
case updateFontIsSerif(Bool)
case addBookmark
case openBookmarks
case openAddressBar
case closeAddressBar
case navigateTo(String)
}
fileprivate final class Node: ViewControllerTracingNode {
@@ -271,7 +323,6 @@ public class BrowserScreen: ViewController, MinimizableController {
private let contentContainerView = UIView()
fileprivate let contentNavigationContainer = ComponentView<Empty>()
fileprivate var content: [BrowserContent] = []
fileprivate var contentState: BrowserContentState?
private var contentStateDisposable = MetaDisposable()
@@ -292,13 +343,20 @@ public class BrowserScreen: ViewController, MinimizableController {
self.presentationState = BrowserPresentationState(
fontState: BrowserPresentationState.FontState(size: 100, isSerif: false),
isSearching: false, searchResultIndex: 0, searchResultCount: 0, searchQueryIsEmpty: true
isSearching: false,
searchResultIndex: 0,
searchResultCount: 0,
searchQueryIsEmpty: true,
addressFocused: false
)
super.init()
self.pushContent(controller.subject, transition: .immediate)
if let content = self.content.last {
content.addToRecentlyVisited()
}
self.performAction.connect { [weak self] action in
guard let self, let content = self.content.last, let url = self.contentState?.url else {
return
@@ -341,7 +399,7 @@ public class BrowserScreen: ViewController, MinimizableController {
let text: String
var savedMessages = false
if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId {
text = presentationData.strings.WebBrowser_LinkForwardTooltip_SavedMessages_One
text = presentationData.strings.WebBrowser_LinkAddedToBookmarks
savedMessages = true
} else {
if peers.count == 1, let peer = peers.first {
@@ -388,7 +446,7 @@ public class BrowserScreen: ViewController, MinimizableController {
case .openSettings:
self.openSettings()
case let .updateSearchActive(active):
self.updatePresentationState(animated: true, { state in
self.updatePresentationState(transition: .easeInOut(duration: 0.2), { state in
var updatedState = state
updatedState.isSearching = active
updatedState.searchQueryIsEmpty = true
@@ -485,10 +543,31 @@ public class BrowserScreen: ViewController, MinimizableController {
content.updateFontState(self.presentationState.fontState)
case .addBookmark:
if let content = self.content.last {
self.addBookmark(content.currentState.url)
self.addBookmark(content.currentState.url, showArrow: true)
}
case .openBookmarks:
break
self.openBookmarks()
case .openAddressBar:
self.updatePresentationState(transition: .spring(duration: 0.4), { state in
var updatedState = state
updatedState.addressFocused = true
return updatedState
})
case .closeAddressBar:
self.updatePresentationState(transition: .spring(duration: 0.4), { state in
var updatedState = state
updatedState.addressFocused = false
return updatedState
})
case let .navigateTo(address):
if let content = self.content.last as? BrowserWebContent {
content.navigateTo(address: address)
}
self.updatePresentationState(transition: .spring(duration: 0.4), { state in
var updatedState = state
updatedState.addressFocused = false
return updatedState
})
}
}
@@ -517,9 +596,9 @@ public class BrowserScreen: ViewController, MinimizableController {
self.view.addSubview(self.contentContainerView)
}
func updatePresentationState(animated: Bool = false, _ f: (BrowserPresentationState) -> BrowserPresentationState) {
func updatePresentationState(transition: ComponentTransition = .immediate, _ f: (BrowserPresentationState) -> BrowserPresentationState) {
self.presentationState = f(self.presentationState)
self.requestLayout(transition: animated ? .easeInOut(duration: 0.2) : .immediate)
self.requestLayout(transition: transition)
}
func pushContent(_ content: BrowserScreen.Subject, transition: ComponentTransition) {
@@ -536,6 +615,10 @@ public class BrowserScreen: ViewController, MinimizableController {
self.openPeer(peer)
}
browserContent = instantPageContent
case let .document(file):
browserContent = BrowserDocumentContent(context: self.context, presentationData: self.presentationData, file: file)
case let .pdfDocument(file):
browserContent = BrowserPdfContent(context: self.context, presentationData: self.presentationData, file: file)
}
browserContent.pushContent = { [weak self] content in
guard let self else {
@@ -596,7 +679,7 @@ public class BrowserScreen: ViewController, MinimizableController {
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), animated: true))
}
func addBookmark(_ url: String) {
func addBookmark(_ url: String, showArrow: Bool) {
let _ = enqueueMessages(
account: self.context.account,
peerId: self.context.account.peerId,
@@ -615,7 +698,9 @@ public class BrowserScreen: ViewController, MinimizableController {
).start()
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: presentationData.strings.WebBrowser_LinkAddedToBookmarks), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in
let lastController = self.controller?.navigationController?.viewControllers.last as? ViewController
lastController?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: presentationData.strings.WebBrowser_LinkAddedToBookmarks), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in
if let self, action == .info {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
@@ -708,6 +793,20 @@ public class BrowserScreen: ViewController, MinimizableController {
}, animated: true)
}
func openBookmarks() {
guard let url = self.contentState?.url else {
return
}
let controller = BrowserBookmarksScreen(context: self.context, url: url, openUrl: { [weak self] url in
if let self {
self.performAction.invoke(.navigateTo(url))
}
}, addBookmark: { [weak self] in
self?.addBookmark(url, showArrow: false)
})
self.controller?.push(controller)
}
func openSettings() {
guard let referenceView = self.componentHost.findTaggedView(tag: settingsTag) as? ReferenceButtonComponent.View else {
return
@@ -736,7 +835,7 @@ public class BrowserScreen: ViewController, MinimizableController {
let _ = (settings
|> deliverOnMainQueue).start(next: { [weak self] settings in
guard let self, let controller = self.controller else {
guard let self, let controller = self.controller, let contentState = self.contentState else {
return
}
@@ -771,7 +870,7 @@ public class BrowserScreen: ViewController, MinimizableController {
defaultWebBrowser = "safari"
}
let url = self.contentState?.url ?? ""
let url = contentState.url
let openInOptions = availableOpenInOptions(context: self.context, item: .url(url: url))
let openInTitle: String
let openInUrl: String
@@ -787,40 +886,52 @@ public class BrowserScreen: ViewController, MinimizableController {
openInUrl = url
}
let items: [ContextMenuItem] = [
.custom(fontItem, false),
.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_FontSanFrancisco, icon: forceIsSerif ? emptyIcon : checkIcon, action: { (controller, action) in
var items: [ContextMenuItem] = []
items.append(.custom(fontItem, false))
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_FontSanFrancisco, icon: forceIsSerif ? emptyIcon : checkIcon, action: { (controller, action) in
performAction.invoke(.updateFontIsSerif(false))
action(.default)
})),
.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_FontNewYork, textFont: .custom(font: Font.with(size: 17.0, design: .serif, traits: []), height: nil, verticalOffset: nil), icon: forceIsSerif ? checkIcon : emptyIcon, action: { (controller, action) in
})))
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_FontNewYork, textFont: .custom(font: Font.with(size: 17.0, design: .serif, traits: []), height: nil, verticalOffset: nil), icon: forceIsSerif ? checkIcon : emptyIcon, action: { (controller, action) in
performAction.invoke(.updateFontIsSerif(true))
action(.default)
})),
.separator,
.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Reload, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Reload"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
})))
items.append(.separator)
if case .webPage = contentState.contentType {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Reload, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Reload"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
performAction.invoke(.reload)
action(.default)
})),
.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Search"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
})))
}
if [.webPage, .instantPage].contains(contentState.contentType) {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Search"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
performAction.invoke(.updateSearchActive(true))
action(.default)
})),
.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Share, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
performAction.invoke(.share)
action(.default)
})),
.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_AddBookmark, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
})))
}
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Share, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
performAction.invoke(.share)
action(.default)
})))
if [.webPage, .instantPage].contains(contentState.contentType) {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_AddBookmark, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
performAction.invoke(.addBookmark)
action(.default)
})),
.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_OpenInBrowser(openInTitle).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] (controller, action) in
})))
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_OpenInBrowser(openInTitle).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] (controller, action) in
if let self {
self.context.sharedContext.applicationBindings.openUrl(openInUrl)
}
action(.default)
}))
]
})))
}
let contextController = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))))
self.controller?.present(contextController, in: .window(.root))
@@ -1050,21 +1161,34 @@ public class BrowserScreen: ViewController, MinimizableController {
public enum Subject {
case webPage(url: String)
case instantPage(webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation)
case document(file: TelegramMediaFile)
case pdfDocument(file: TelegramMediaFile)
}
private let context: AccountContext
private let subject: Subject
var openPreviousOnClose = false
public static let supportedDocumentMimeTypes: [String] = [
"text/plain",
"text/rtf",
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
]
public init(context: AccountContext, subject: Subject) {
self.context = context
self.subject = subject
super.init(navigationBarPresentationData: nil)
self.navigationPresentation = .modal
self.navigationPresentation = .modalInCompactLayout
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown)
@@ -1107,6 +1231,8 @@ public class BrowserScreen: ViewController, MinimizableController {
return contentState.favicon
case .instantPage:
return UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon")?.withRenderingMode(.alwaysTemplate)
case .document:
return nil
}
}
return nil