From f38e31dc224b2fa33f734a630c532939980796e8 Mon Sep 17 00:00:00 2001 From: Kylmakalle Date: Sun, 29 Sep 2024 15:56:21 +0300 Subject: [PATCH] WIP Injecting SwiftUI via LegacyController --- Swiftgram/Playground/BUILD | 13 +- Swiftgram/Playground/README.md | 12 +- .../Sources/SwiftUIViewController.swift | 459 ++++++++++-------- Swiftgram/Playground/generate_project.py | 78 --- Telegram/BUILD | 8 +- build-system/Make/ProjectGeneration.py | 7 + .../LegacyComponents/TGMediaVideoConverter.h | 5 + 7 files changed, 279 insertions(+), 303 deletions(-) delete mode 100755 Swiftgram/Playground/generate_project.py diff --git a/Swiftgram/Playground/BUILD b/Swiftgram/Playground/BUILD index fecebb3c58..87aaa2b6be 100644 --- a/Swiftgram/Playground/BUILD +++ b/Swiftgram/Playground/BUILD @@ -6,7 +6,7 @@ load( "xcodeproj", ) load( - "//Swiftgram/Playground:custom_bazel_path.bzl", "custom_bazel_path" + "@build_configuration//:variables.bzl", "telegram_bazel_path" ) objc_library( @@ -24,6 +24,12 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/Display:Display", "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/LegacyUI:LegacyUI", + "//submodules/LegacyComponents:LegacyComponents", + "//submodules/MediaPlayer:UniversalMediaPlayer", + ], + data = [ + "//Telegram:GeneratedPresentationStrings/Resources/PresentationStrings.data", ], visibility = ["//visibility:public"], ) @@ -38,12 +44,15 @@ ios_application( infoplists = ["Resources/Info.plist"], minimum_os_version = "14.0", visibility = ["//visibility:public"], + strings = [ + "//Telegram:AppStringResources", + ], launch_storyboard = "Resources/LaunchScreen.storyboard", deps = [":PlaygroundMain", ":PlaygroundLib"], ) xcodeproj( - bazel_path = custom_bazel_path(), + bazel_path = telegram_bazel_path, name = "Playground_xcodeproj", build_mode = "bazel", project_name = "Playground", diff --git a/Swiftgram/Playground/README.md b/Swiftgram/Playground/README.md index 59cee5b336..cdb7a64699 100644 --- a/Swiftgram/Playground/README.md +++ b/Swiftgram/Playground/README.md @@ -4,17 +4,7 @@ Small app to quickly iterate on components testing without building an entire me ## Generate Xcode project -### From root - -```shell -./Swiftgram/Playground/generate_project.py -``` - -### From current directory - -```shell -./generate_project.py -``` +Same as main project described in [../../Readme.md](../../Readme.md), but with `--target="Swiftgram/Playground"` parameter. ## Run generated project on simulator diff --git a/Swiftgram/Playground/Sources/SwiftUIViewController.swift b/Swiftgram/Playground/Sources/SwiftUIViewController.swift index f4963692f0..0291b9bfc9 100644 --- a/Swiftgram/Playground/Sources/SwiftUIViewController.swift +++ b/Swiftgram/Playground/Sources/SwiftUIViewController.swift @@ -3,6 +3,8 @@ import Display import Foundation import SwiftUI import UIKit +import LegacyUI +import TelegramPresentationData public class SwiftUIViewControllerInteraction { let push: (ViewController) -> Void @@ -27,230 +29,265 @@ public class SwiftUIViewControllerInteraction { self.dismiss = dismiss } } +// +//public protocol SwiftUIView: View { +// var controllerInteraction: SwiftUIViewControllerInteraction? { get set } +//} +// +//struct MySwiftUIView: SwiftUIView { +// var controllerInteraction: SwiftUIViewControllerInteraction? +// +// +// var num: Int64 +// +// var body: some View { +// Color.orange +// } +//} +// +//struct CustomButtonStyle: ButtonStyle { +// func makeBody(configuration: Configuration) -> some View { +// configuration.label +// .padding() +// .background(Color.blue) +// .foregroundColor(.white) +// .cornerRadius(8) +// .frame(height: 44) // Set a fixed height for all buttons +// } +//} +// +//private final class SwiftUIViewControllerNode: ASDisplayNode { +// private let hostingController: UIHostingController +// private var isDismissed = false +// private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? +// +// init(swiftUIView: Content) { +// self.hostingController = UIHostingController(rootView: swiftUIView) +// super.init() +// +// // For debugging +// self.backgroundColor = .red.withAlphaComponent(0.3) +// hostingController.view.backgroundColor = .blue.withAlphaComponent(0.3) +// } +// +// override func didLoad() { +// super.didLoad() +// +// // Defer the setup to ensure we have a valid view controller hierarchy +// DispatchQueue.main.async { [weak self] in +// self?.setupHostingController() +// } +// } +// +// private func setupHostingController() { +// guard let viewController = findViewController() else { +// assert(true, "Error: Could not find a parent view controller") +// return +// } +// +// viewController.addChild(hostingController) +// view.addSubview(hostingController.view) +// hostingController.didMove(toParent: viewController) +// +// // Ensure the hosting controller's view has a size +// hostingController.view.frame = self.bounds +// +// print("SwiftUIViewControllerNode setup - Node frame: \(self.frame), Hosting view frame: \(hostingController.view.frame)") +// } +// +// private func findViewController() -> UIViewController? { +// var responder: UIResponder? = self.view +// while let nextResponder = responder?.next { +// if let viewController = nextResponder as? UIViewController { +// return viewController +// } +// responder = nextResponder +// } +// return nil +// } +// +// override func layout() { +// super.layout() +// hostingController.view.frame = self.bounds +// print("SwiftUIViewControllerNode layout - Node frame: \(self.frame), Hosting view frame: \(hostingController.view.frame)") +// } +// +// func containerLayoutUpdated( +// layout: ContainerViewLayout, +// navigationHeight: CGFloat, +// transition: ContainedViewLayoutTransition +// ) { +// if self.isDismissed { +// return +// } +// +// self.validLayout = (layout, navigationHeight) +// +// let frame = CGRect( +// origin: CGPoint(x: 0, y: 0), +// size: CGSize( +// width: layout.size.width, +// height: layout.size.height +// ) +// ) +// +// transition.updateFrame(node: self, frame: frame) +// +// print("containerLayoutUpdated - New frame: \(frame)") +// +// // Ensure hosting controller view is updated +// hostingController.view.frame = bounds +// } +// +// func animateOut(completion: @escaping () -> Void) { +// guard let (layout, navigationHeight) = validLayout else { +// completion() +// return +// } +// self.isDismissed = true +// let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) +// +// let frame = CGRect( +// origin: CGPoint(x: 0, y: 0), +// size: CGSize( +// width: layout.size.width, +// height: layout.size.height +// ) +// ) +// +// transition.updateFrame(node: self, frame: frame, completion: { _ in +// completion() +// }) +// } +// +// override func didEnterHierarchy() { +// super.didEnterHierarchy() +// print("SwiftUIViewControllerNode entered hierarchy") +// } +// +// override func didExitHierarchy() { +// super.didExitHierarchy() +// hostingController.willMove(toParent: nil) +// hostingController.view.removeFromSuperview() +// hostingController.removeFromParent() +// print("SwiftUIViewControllerNode exited hierarchy") +// } +//} +// +//public final class SwiftUIViewController: ViewController { +// private var swiftUIView: Content +// +// public init( +// _ swiftUIView: Content, +// navigationBarTheme: NavigationBarTheme = NavigationBarTheme( +// buttonColor: ACCENT_COLOR, +// disabledButtonColor: .gray, +// primaryTextColor: .black, +// backgroundColor: .clear, +// enableBackgroundBlur: true, +// separatorColor: .gray, +// badgeBackgroundColor: THEME.navigationBar.badgeBackgroundColor, +// badgeStrokeColor: THEME.navigationBar.badgeStrokeColor, +// badgeTextColor: THEME.navigationBar.badgeTextColor +// ), +// navigationBarStrings: NavigationBarStrings = NavigationBarStrings( +// back: "Back", +// close: "Close" +// ) +// ) { +// self.swiftUIView = swiftUIView +// super.init(navigationBarPresentationData: NavigationBarPresentationData( +// theme: navigationBarTheme, +// strings: navigationBarStrings +// )) +// +// self.swiftUIView.controllerInteraction = SwiftUIViewControllerInteraction( +// push: { [weak self] c in +// guard let strongSelf = self else { return } +// strongSelf.push(c) +// }, +// present: { [weak self] c, context, args in +// guard let strongSelf = self else { return } +// strongSelf.present(c, in: context, with: args) +// }, +// dismiss: { [weak self] animated, completion in +// guard let strongSelf = self else { return } +// strongSelf.dismiss(animated: animated, completion: completion) +// } +// ) +// } +// +// @available(*, unavailable) +// required init(coder _: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// override public func loadDisplayNode() { +// self.displayNode = SwiftUIViewControllerNode(swiftUIView: swiftUIView) +// } +// +// override public func containerLayoutUpdated( +// _ layout: ContainerViewLayout, +// transition: ContainedViewLayoutTransition +// ) { +// super.containerLayoutUpdated(layout, transition: transition) +// +// (self.displayNode as! SwiftUIViewControllerNode).containerLayoutUpdated( +// layout: layout, +// navigationHeight: navigationLayout(layout: layout).navigationFrame.maxY, +// transition: transition +// ) +// } +// +// public func animateOut(completion: @escaping () -> Void) { +// (self.displayNode as! SwiftUIViewControllerNode) +// .animateOut(completion: completion) +// } +//} +// +// +//func mySwiftUIViewController(_ num: Int64) -> ViewController { +// let controller = SwiftUIViewController(MySwiftUIView(num: num)) +// controller.title = "Controller: \(num)" +// return controller +//} + public protocol SwiftUIView: View { var controllerInteraction: SwiftUIViewControllerInteraction? { get set } - var navigationHeight: CGFloat { get set } } struct MySwiftUIView: SwiftUIView { var controllerInteraction: SwiftUIViewControllerInteraction? - @Binding var navigationHeight: CGFloat - - + var num: Int64 var body: some View { Color.orange - .padding(.top, 2.0 * (_navigationHeight ?? 0)) +// VStack { +// Button("Push") { +// self.controllerInteraction?.push(mySwiftUIViewController(num + 1)) +// } +// Button("Modal") { +// self.controllerInteraction?.present(mySwiftUIViewController(num + 1), .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) +// } +// Button("Dismiss") { +// self.controllerInteraction?.dismiss(true, {}) +// } +// } } } -struct CustomButtonStyle: ButtonStyle { - func makeBody(configuration: Configuration) -> some View { - configuration.label - .padding() - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(8) - .frame(height: 44) // Set a fixed height for all buttons - } -} - -private final class SwiftUIViewControllerNode: ASDisplayNode { - private let hostingController: UIHostingController - private var isDismissed = false - private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? - - init(swiftUIView: Content) { - self.hostingController = UIHostingController(rootView: swiftUIView) - super.init() - - // For debugging - self.backgroundColor = .red.withAlphaComponent(0.3) - hostingController.view.backgroundColor = .blue.withAlphaComponent(0.3) - } - - override func didLoad() { - super.didLoad() - - // Defer the setup to ensure we have a valid view controller hierarchy - DispatchQueue.main.async { [weak self] in - self?.setupHostingController() - } - } - - private func setupHostingController() { - guard let viewController = findViewController() else { - assert(true, "Error: Could not find a parent view controller") - return - } - - viewController.addChild(hostingController) - view.addSubview(hostingController.view) - hostingController.didMove(toParent: viewController) - - // Ensure the hosting controller's view has a size - hostingController.view.frame = self.bounds - - print("SwiftUIViewControllerNode setup - Node frame: \(self.frame), Hosting view frame: \(hostingController.view.frame)") - } - - private func findViewController() -> UIViewController? { - var responder: UIResponder? = self.view - while let nextResponder = responder?.next { - if let viewController = nextResponder as? UIViewController { - return viewController - } - responder = nextResponder - } - return nil - } - - override func layout() { - super.layout() - hostingController.view.frame = self.bounds - print("SwiftUIViewControllerNode layout - Node frame: \(self.frame), Hosting view frame: \(hostingController.view.frame)") - } - - func containerLayoutUpdated( - layout: ContainerViewLayout, - navigationHeight: CGFloat, - transition: ContainedViewLayoutTransition - ) { - if self.isDismissed { - return - } - - self.validLayout = (layout, navigationHeight) - - let frame = CGRect( - origin: CGPoint(x: 0, y: 0), - size: CGSize( - width: layout.size.width, - height: layout.size.height - ) - ) - - transition.updateFrame(node: self, frame: frame) - - print("containerLayoutUpdated - New frame: \(frame)") - - // Ensure hosting controller view is updated - hostingController.view.frame = bounds - hostingController.rootView.navigationHeight = navigationHeight - } - - func animateOut(completion: @escaping () -> Void) { - guard let (layout, navigationHeight) = validLayout else { - completion() - return - } - self.isDismissed = true - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - - let frame = CGRect( - origin: CGPoint(x: 0, y: 0), - size: CGSize( - width: layout.size.width, - height: layout.size.height - ) - ) - - transition.updateFrame(node: self, frame: frame, completion: { _ in - completion() - }) - hostingController.rootView.navigationHeight = navigationHeight - } - - override func didEnterHierarchy() { - super.didEnterHierarchy() - print("SwiftUIViewControllerNode entered hierarchy") - } - - override func didExitHierarchy() { - super.didExitHierarchy() - hostingController.willMove(toParent: nil) - hostingController.view.removeFromSuperview() - hostingController.removeFromParent() - print("SwiftUIViewControllerNode exited hierarchy") - } -} - -public final class SwiftUIViewController: ViewController { - private var swiftUIView: Content - - public init( - _ swiftUIView: Content, - navigationBarTheme: NavigationBarTheme = NavigationBarTheme( - buttonColor: ACCENT_COLOR, - disabledButtonColor: .gray, - primaryTextColor: .black, - backgroundColor: .clear, - enableBackgroundBlur: true, - separatorColor: .gray, - badgeBackgroundColor: THEME.navigationBar.badgeBackgroundColor, - badgeStrokeColor: THEME.navigationBar.badgeStrokeColor, - badgeTextColor: THEME.navigationBar.badgeTextColor - ), - navigationBarStrings: NavigationBarStrings = NavigationBarStrings( - back: "Back", - close: "Close" - ) - ) { - self.swiftUIView = swiftUIView - super.init(navigationBarPresentationData: NavigationBarPresentationData( - theme: navigationBarTheme, - strings: navigationBarStrings - )) - - self.swiftUIView.controllerInteraction = SwiftUIViewControllerInteraction( - push: { [weak self] c in - guard let strongSelf = self else { return } - strongSelf.push(c) - }, - present: { [weak self] c, context, args in - guard let strongSelf = self else { return } - strongSelf.present(c, in: context, with: args) - }, - dismiss: { [weak self] animated, completion in - guard let strongSelf = self else { return } - strongSelf.dismiss(animated: animated, completion: completion) - } - ) - } - - @available(*, unavailable) - required init(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public func loadDisplayNode() { - self.displayNode = SwiftUIViewControllerNode(swiftUIView: swiftUIView) - } - - override public func containerLayoutUpdated( - _ layout: ContainerViewLayout, - transition: ContainedViewLayoutTransition - ) { - super.containerLayoutUpdated(layout, transition: transition) - - (self.displayNode as! SwiftUIViewControllerNode).containerLayoutUpdated( - layout: layout, - navigationHeight: navigationLayout(layout: layout).navigationFrame.maxY, - transition: transition - ) - } - - public func animateOut(completion: @escaping () -> Void) { - (self.displayNode as! SwiftUIViewControllerNode) - .animateOut(completion: completion) - } -} - - func mySwiftUIViewController(_ num: Int64) -> ViewController { - let controller = SwiftUIViewController(MySwiftUIView(num: num)) - controller.title = "Controller: \(num)" - return controller + let legacyController = LegacyController(presentation: .navigation, theme: defaultPresentationTheme, strings: defaultPresentationStrings) + legacyController.statusBar.statusBarStyle = defaultPresentationTheme.rootController.statusBarStyle.style + legacyController.title = "Controller: root" + + let controller = UIHostingController(rootView: MySwiftUIView(num: num)) + legacyController.bind(controller: controller) + legacyController.addChild(controller) + controller.didMove(toParent: legacyController) + + + return legacyController } diff --git a/Swiftgram/Playground/generate_project.py b/Swiftgram/Playground/generate_project.py deleted file mode 100755 index 1124292600..0000000000 --- a/Swiftgram/Playground/generate_project.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python3 - -from contextlib import contextmanager -import os -import subprocess -import sys -import shutil -import textwrap - -# Import the locate_bazel function -sys.path.append( - os.path.join(os.path.dirname(__file__), "..", "..", "build-system", "Make") -) -from BazelLocation import locate_bazel - - -@contextmanager -def cwd(path): - oldpwd = os.getcwd() - os.chdir(path) - try: - yield - finally: - os.chdir(oldpwd) - - -def main(): - # Get the current script directory - current_script_dir = os.path.dirname(os.path.abspath(__file__)) - with cwd(os.path.join(current_script_dir, "..", "..")): - bazel_path = locate_bazel(os.getcwd()) - # 1. Kill all Xcode processes - subprocess.run(["killall", "Xcode"], check=False) - - # 2. Delete xcodeproj.bazelrc if it exists and write a new one - bazelrc_path = os.path.join(current_script_dir, "..", "..", "xcodeproj.bazelrc") - if os.path.exists(bazelrc_path): - os.remove(bazelrc_path) - - with open(bazelrc_path, "w") as f: - f.write( - textwrap.dedent( - """ - build --announce_rc - build --features=swift.use_global_module_cache - build --verbose_failures - build --features=swift.enable_batch_mode - build --features=-swift.debug_prefix_map - # build --disk_cache= - - build --swiftcopt=-no-warnings-as-errors - build --copt=-Wno-error - """ - ) - ) - - # 3. Delete the Xcode project if it exists - xcode_project_path = os.path.join(current_script_dir, "Playground.xcodeproj") - if os.path.exists(xcode_project_path): - shutil.rmtree(xcode_project_path) - - # 4. Write content to generate_project.py - generate_project_path = os.path.join(current_script_dir, "custom_bazel_path.bzl") - with open(generate_project_path, "w") as f: - f.write("def custom_bazel_path():\n") - f.write(f' return "{bazel_path}"\n') - - # 5. Run xcodeproj generator - working_dir = os.path.join(current_script_dir, "..", "..") - bazel_command = f'"{bazel_path}" run //Swiftgram/Playground:Playground_xcodeproj' - subprocess.run(bazel_command, shell=True, cwd=working_dir, check=True) - - # 5. Open Xcode project - subprocess.run(["open", xcode_project_path], check=True) - - -if __name__ == "__main__": - main() diff --git a/Telegram/BUILD b/Telegram/BUILD index 32cf497229..a4b8f9bd08 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -138,6 +138,10 @@ genrule( "GeneratedPresentationStrings/Sources/PresentationStrings.m", "GeneratedPresentationStrings/Resources/PresentationStrings.data", ], + # MARK: Swiftgram + visibility = [ + "//visibility:public", + ], ) minimum_os_version = "12.0" @@ -253,7 +257,9 @@ filegroup( "//Swiftgram/SGStrings:SGLocalizableStrings", ] + [ "{}.lproj/Localizable.strings".format(language) for language in empty_languages - ] + ], + # MARK: Swiftgram + visibility = ["//visibility:public",], ) filegroup( diff --git a/build-system/Make/ProjectGeneration.py b/build-system/Make/ProjectGeneration.py index 953f4caa4c..4f8fe95d8c 100644 --- a/build-system/Make/ProjectGeneration.py +++ b/build-system/Make/ProjectGeneration.py @@ -34,6 +34,9 @@ def generate_xcodeproj(build_environment: BuildEnvironment, disable_extensions, project_bazel_arguments.append(argument) project_bazel_arguments += ['--override_repository=build_configuration={}'.format(configuration_path)] + if target_name == "Swiftgram/Playground": + project_bazel_arguments += ["--swiftcopt=-no-warnings-as-errors", "--copt=-Wno-error"]#, "--swiftcopt=-DSWIFTGRAM_PLAYGROUND", "--copt=-DSWIFTGRAM_PLAYGROUND=1"] + if target_name == 'Telegram': if disable_extensions: project_bazel_arguments += ['--//{}:disableExtensions'.format(app_target)] @@ -51,6 +54,10 @@ def generate_xcodeproj(build_environment: BuildEnvironment, disable_extensions, call_executable(bazel_generate_arguments) # MARK: Swiftgram + if target_name == "Swiftgram/Playground": + xcodeproj_path = 'Swiftgram/Playground/Playground.xcodeproj' + call_executable(['open', xcodeproj_path]) + return xcodeproj_path = 'Telegram/Swiftgram.xcodeproj' call_executable(['open', xcodeproj_path]) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h index e110fed1a7..3686961190 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h @@ -2,6 +2,11 @@ #import +// MARK: Swiftgram +#import +#import +// + @interface TGMediaVideoFileWatcher : NSObject { NSURL *_fileURL;