diff --git a/Project.swift b/Project.swift index b358e2a..dabac2a 100644 --- a/Project.swift +++ b/Project.swift @@ -18,12 +18,12 @@ class iBoxFactory: ProjectFactory { let dependencies: [TargetDependency] = [ .external(name: "SnapKit"), + .external(name: "SwiftSoup"), .target(name: "iBoxShareExtension") ] let iBoxShareExtensionDependencies: [TargetDependency] = [ - .external(name: "SnapKit"), - .external(name: "SwiftSoup") + .external(name: "SnapKit") ] private let appInfoPlist: [String: Plist.Value] = [ @@ -92,8 +92,8 @@ class iBoxFactory: ProjectFactory { bundleId: "\(bundleId).ShareExtension", deploymentTarget: .iOS(targetVersion: iosVersion, devices: [.iphone]), infoPlist: .extendingDefault(with: shareExtensionInfoPlist), - sources: ["ShareExtension/**"], - resources: [], + sources: ["ShareExtension/Sources/**"], + resources: ["ShareExtension/Resources/**"], dependencies: iBoxShareExtensionDependencies ) diff --git a/ShareExtension/Resources/Media.xcassets/128.imageset/128 1.png b/ShareExtension/Resources/Media.xcassets/128.imageset/128 1.png new file mode 100644 index 0000000..077d76c Binary files /dev/null and b/ShareExtension/Resources/Media.xcassets/128.imageset/128 1.png differ diff --git a/ShareExtension/Resources/Media.xcassets/128.imageset/128.png b/ShareExtension/Resources/Media.xcassets/128.imageset/128.png new file mode 100644 index 0000000..077d76c Binary files /dev/null and b/ShareExtension/Resources/Media.xcassets/128.imageset/128.png differ diff --git a/ShareExtension/Resources/Media.xcassets/128.imageset/Contents.json b/ShareExtension/Resources/Media.xcassets/128.imageset/Contents.json new file mode 100644 index 0000000..8b99860 --- /dev/null +++ b/ShareExtension/Resources/Media.xcassets/128.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "128.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "128 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ShareExtension/Resources/Media.xcassets/Contents.json b/ShareExtension/Resources/Media.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ShareExtension/Resources/Media.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift deleted file mode 100644 index d7d0a2b..0000000 --- a/ShareExtension/ShareViewController.swift +++ /dev/null @@ -1,182 +0,0 @@ -// -// ShareViewController.swift -// iBoxWebShareExtension -// -// Created by Chan on 2/8/24. -// - -import UIKit -import Social -import UniformTypeIdentifiers - -import SnapKit -import SwiftSoup - -@objc(CustomShareViewController) -class CustomShareViewController: UIViewController { - - var backgroundView = ShareExtensionBackGroundView() - var dataURL: String? - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupProperty() - setupHierarchy() - setupLayout() - extractSharedURL() - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundView.delegate = self - } - - private func setupHierarchy() { - view.addSubview(backgroundView) - } - - private func setupLayout() { - backgroundView.snp.makeConstraints { make in - make.trailing.leading.equalToSuperview().inset(30) - make.center.equalToSuperview().offset(-20) - make.height.equalTo(120) - } - } - - func getAppGroupUserDefaults() { - let defaults = UserDefaults(suiteName: "group.com.iBox") - if let data = defaults?.string(forKey: "share") { - print("ShareViewController: URL 가져오기: \(data)") - } - } - - func hideExtensionWithCompletionHandler(completion: @escaping (Bool) -> Void) { - UIView.animate(withDuration: 0.3, animations: { - self.navigationController?.view.transform = CGAffineTransform(translationX: 0, y:self.navigationController!.view.frame.size.height) - }, completion: completion) - } - - // MARK: IBAction - - @IBAction func cancel() { - self.hideExtensionWithCompletionHandler(completion: { _ in - self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) - }) - } - - @objc func openURL(_ url: URL) -> Bool { - self.hideExtensionWithCompletionHandler(completion: { _ in - self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) - }) - - var responder: UIResponder? = self - while responder != nil { - if let application = responder as? UIApplication { - return application.perform(#selector(openURL(_:)), with: url) != nil - } - responder = responder?.next - } - return false - } - - func extractSharedURL() { - guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem else { return } - - for attachment in extensionItem.attachments ?? [] { - if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) { - attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] (data, error) in - DispatchQueue.main.async { - if let url = data as? URL, error == nil { - self?.dataURL = url.absoluteString - print("Shared URL: \(url.absoluteString)") - } else { - print("Failed to retrieve URL: \(String(describing: error))") - } - } - } - break - } - } - } - - func fetchAndParseMetadata(from url: URL, completion: @escaping (Metadata) -> Void) { - URLSession.shared.dataTask(with: url) { data, response, error in - guard let data = data, error == nil else { - print("Failed to fetch data: \(String(describing: error))") - return - } - - let encodingName = (response as? HTTPURLResponse)?.textEncodingName ?? "utf-8" - let encoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString))) - - if let htmlContent = String(data: data, encoding: encoding) { - do { - let doc: Document = try SwiftSoup.parse(htmlContent) - let title: String? = try doc.title() - - let faviconSelectors = ["link[rel='shortcut icon']", "link[rel='icon']", "link[rel='apple-touch-icon']"] - var faviconUrl: String? = nil - - for selector in faviconSelectors { - if let faviconLink: Element = try doc.select(selector).first() { - if var href = try? faviconLink.attr("href"), !href.isEmpty { - if href.starts(with: "/") { - href = url.scheme! + "://" + url.host! + href - } else if !href.starts(with: "http") { - href = url.scheme! + "://" + url.host! + "/" + href - } - faviconUrl = href - break - } - } - } - - if faviconUrl == nil { - faviconUrl = url.scheme! + "://" + url.host! + "/favicon.ico" - } - - let decodedUrlString = url.absoluteString.removingPercentEncoding ?? url.absoluteString - let metadata = Metadata(title: title, faviconUrl: faviconUrl, url: decodedUrlString) - - DispatchQueue.main.async { - completion(metadata) - } - } catch { - print("Failed to parse HTML: \(error.localizedDescription)") - } - } - }.resume() - } -} - -extension CustomShareViewController: ShareExtensionBackGroundViewDelegate { - - func didTapCancel() { - cancel() - } - - func didTapOpenApp() { - guard let sharedURL = dataURL, let url = URL(string: sharedURL) else { - print("Share extension error") - return - } - fetchAndParseMetadata(from: url) { metadata in - dump(metadata) - let encodedTitle = metadata.title?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" - let encodedData = metadata.url?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" - let encodedFaviconUrl = metadata.faviconUrl?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" - let urlString = "iBox://url?title=\(encodedTitle)&data=\(encodedData)&faviconUrl=\(encodedFaviconUrl)" - - if let openUrl = URL(string: urlString) { - if self.openURL(openUrl) { - print("iBox 앱이 성공적으로 열렸습니다.") - } else { - print("iBox 앱을 열 수 없습니다.") - } - } - } - } -} diff --git a/ShareExtension/Sources/Extension/ShareExtension+UIColor+Extension.swift b/ShareExtension/Sources/Extension/ShareExtension+UIColor+Extension.swift new file mode 100644 index 0000000..817e21e --- /dev/null +++ b/ShareExtension/Sources/Extension/ShareExtension+UIColor+Extension.swift @@ -0,0 +1,22 @@ +// +// ShareExtension+UIColor+Extension.swift +// iBoxShareExtension +// +// Created by Chan on 4/14/24. +// + +import UIKit + +extension UIColor { + + convenience init(hex: UInt, alpha: CGFloat = 1.0) { + self.init( + red: CGFloat((hex & 0xFF0000) >> 16) / 255.0, + green: CGFloat((hex & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(hex & 0x0000FF) / 255.0, + alpha: CGFloat(alpha) + ) + } + + static let box2 = UIColor(hex: 0xFF9548) +} diff --git a/ShareExtension/Sources/Extension/ShareExtension+UIbutton+Extension.swift b/ShareExtension/Sources/Extension/ShareExtension+UIbutton+Extension.swift new file mode 100644 index 0000000..466fe44 --- /dev/null +++ b/ShareExtension/Sources/Extension/ShareExtension+UIbutton+Extension.swift @@ -0,0 +1,22 @@ +// +// ShareExtension+UIbutton+Extension.swift +// iBoxShareExtension +// +// Created by Chan on 4/14/24. +// + +import UIKit + +extension UIButton { + func setBackgroundColor(_ color: UIColor, for state: UIControl.State) { + UIGraphicsBeginImageContext(CGSize(width: 1.0, height: 1.0)) + guard let context = UIGraphicsGetCurrentContext() else { return } + context.setFillColor(color.cgColor) + context.fill(CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)) + + let backgroundImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + setBackgroundImage(backgroundImage, for: state) + } +} diff --git a/ShareExtension/Sources/ShareViewController.swift b/ShareExtension/Sources/ShareViewController.swift new file mode 100644 index 0000000..a0678cd --- /dev/null +++ b/ShareExtension/Sources/ShareViewController.swift @@ -0,0 +1,154 @@ +// +// ShareViewController.swift +// iBoxWebShareExtension +// +// Created by Chan on 2/8/24. +// + +import UIKit +import Social +import UniformTypeIdentifiers + +import SnapKit +import SwiftSoup + +@objc(CustomShareViewController) +class CustomShareViewController: UIViewController { + + var dataURL: String? + var backgroundView = ShareExtensionBackGroundView() + var modalView: UIView = { + let modalview = UIView() + modalview.backgroundColor = UIColor.lightGray.withAlphaComponent(0.1) + return modalview + }() + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setupProperty() + setupHierarchy() + setupLayout() + setupModal() + extractSharedURL() + } + + // MARK: - Setup Methods + + private func setupProperty() { + backgroundView.delegate = self + } + + private func setupHierarchy() { + view.addSubview(modalView) + modalView.addSubview(backgroundView) + } + + private func setupLayout() { + modalView.snp.makeConstraints { make in + make.edges.equalTo(view.safeAreaLayoutGuide) + } + + backgroundView.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(30) + make.centerY.equalToSuperview().inset(20) + make.height.equalTo(140) + } + } + + private func setupModal() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackgroundTap(_:))) + tapGesture.cancelsTouchesInView = false + tapGesture.delegate = self + modalView.addGestureRecognizer(tapGesture) + } + + func hideExtensionWithCompletionHandler(completion: @escaping (Bool) -> Void) { + UIView.animate(withDuration: 0.3, animations: { + self.navigationController?.view.transform = CGAffineTransform(translationX: 0, y:self.navigationController!.view.frame.size.height) + }, completion: completion) + } + + func extractSharedURL() { + guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem else { return } + + for attachment in extensionItem.attachments ?? [] { + if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) { + attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] (data, error) in + DispatchQueue.main.async { + if let url = data as? URL, error == nil { + self?.dataURL = url.absoluteString + print("Shared URL: \(url.absoluteString)") + } else { + print("Failed to retrieve URL: \(String(describing: error))") + } + } + } + break + } + } + } + + // MARK: IBAction + + @IBAction func cancel() { + self.hideExtensionWithCompletionHandler(completion: { _ in + self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + }) + } + + @objc func openURL(_ url: URL) -> Bool { + self.hideExtensionWithCompletionHandler(completion: { _ in + self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + }) + + var responder: UIResponder? = self + while responder != nil { + if let application = responder as? UIApplication { + return application.perform(#selector(openURL(_:)), with: url) != nil + } + responder = responder?.next + } + return false + } + + @objc func handleBackgroundTap(_ sender: UITapGestureRecognizer) { + let location = sender.location(in: self.view) + if !backgroundView.frame.contains(location) { + cancel() + } + } +} + +extension CustomShareViewController: ShareExtensionBackGroundViewDelegate { + + func didTapCancel() { + cancel() + } + + func didTapOpenApp() { + guard let sharedURL = dataURL else { + print("Share extension error") + return + } + + let urlString = "iBox://url?data=\(sharedURL)" + + if let openUrl = URL(string: urlString) { + if self.openURL(openUrl) { + print("iBox 앱이 성공적으로 열렸습니다.") + } else { + print("iBox 앱을 열 수 없습니다.") + } + } else { + print("url error") + } + } +} + +extension CustomShareViewController: UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } +} diff --git a/ShareExtension/Sources/View/ShareExtensionBackGroundView.swift b/ShareExtension/Sources/View/ShareExtensionBackGroundView.swift new file mode 100644 index 0000000..8fd1643 --- /dev/null +++ b/ShareExtension/Sources/View/ShareExtensionBackGroundView.swift @@ -0,0 +1,145 @@ +// +// BackGroundView.swift +// iBox +// +// Created by Chan on 2/19/24. +// + +import UIKit + +import SnapKit + +protocol ShareExtensionBackGroundViewDelegate: AnyObject { + func didTapCancel() + func didTapOpenApp() +} + +class ShareExtensionBackGroundView: UIView { + + // MARK: - Properties + weak var delegate: ShareExtensionBackGroundViewDelegate? + + // MARK: - UI Components + lazy var stackView: UIStackView = { + let stack = UIStackView() + stack.axis = .horizontal + stack.distribution = .fillProportionally + stack.spacing = 10 + return stack + }() + + lazy var logoImageView: UIImageView = { + let logoImageView = UIImageView() + logoImageView.image = UIImage(named: "128") + logoImageView.contentMode = .scaleAspectFit + logoImageView.setContentHuggingPriority(.required, for: .horizontal) + logoImageView.setContentCompressionResistancePriority(.required, for: .horizontal) + return logoImageView + }() + + lazy var label: UILabel = { + let label = UILabel() + label.text = "이 링크를 iBox 앱에서 여시겠습니까?" + label.font = .systemFont(ofSize: 15) + label.textColor = .label + label.numberOfLines = 3 + label.setContentHuggingPriority(.defaultLow, for: .horizontal) + return label + }() + + lazy var cancelButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) + button.tintColor = .systemGray3 + button.setContentHuggingPriority(.required, for: .horizontal) + button.setContentCompressionResistancePriority(.required, for: .horizontal) + return button + }() + + lazy var divider: UIView = { + let view = UIView() + view.backgroundColor = .lightGray + view.layer.opacity = 0.2 + return view + }() + + lazy var openAppButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "arrow.up.forward.square"), for: .normal) + button.setTitle("앱으로 담아가기", for: .normal) + button.setTitleColor(.black, for: .normal) + button.setBackgroundColor(.clear, for: .normal) + + button.setTitle("앱이 실행됩니다", for: .highlighted) + button.setTitleColor(.darkGray, for: .highlighted) + button.setBackgroundColor(.box2, for: .highlighted) + button.setImage(UIImage(systemName: "heart.fill"), for: .highlighted) + + button.imageView?.contentMode = .scaleAspectFit + button.tintColor = .box2 + + let spacing: CGFloat = 5 + button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: spacing) + button.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: 0) + + return button + }() + + // MARK: - Initializer + override init(frame: CGRect) { + super.init(frame: frame) + setupProperty() + setupHierarchy() + setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setup Methods + private func setupProperty() { + backgroundColor = .systemBackground + clipsToBounds = true + layer.cornerRadius = 15 + + cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside) + openAppButton.addTarget(self, action: #selector(openAppButtonTapped), for: .touchUpInside) + } + + private func setupHierarchy() { + addSubview(stackView) + stackView.addArrangedSubview(logoImageView) + stackView.addArrangedSubview(label) + stackView.addArrangedSubview(cancelButton) + + addSubview(divider) + addSubview(openAppButton) + } + + private func setupLayout() { + stackView.snp.makeConstraints { make in + make.top.leading.trailing.equalToSuperview().inset(20) + } + + divider.snp.makeConstraints { make in + make.top.equalTo(stackView.snp.bottom).offset(10) + make.leading.trailing.equalToSuperview() + make.height.equalTo(1) + } + + openAppButton.snp.makeConstraints { make in + make.top.equalTo(divider.snp.bottom) + make.width.leading.trailing.bottom.equalToSuperview() + } + } + + // MARK: - Action Functions + @objc func cancelButtonTapped() { + delegate?.didTapCancel() + } + + @objc func openAppButtonTapped() { + delegate?.didTapOpenApp() + } +} diff --git a/ShareExtension/View/ShareExtensionBackGroundView.swift b/ShareExtension/View/ShareExtensionBackGroundView.swift deleted file mode 100644 index 04a4b3a..0000000 --- a/ShareExtension/View/ShareExtensionBackGroundView.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// BackGroundView.swift -// iBox -// -// Created by Chan on 2/19/24. -// - -import UIKit - -import SnapKit - -protocol ShareExtensionBackGroundViewDelegate: AnyObject { - func didTapCancel() - func didTapOpenApp() -} - -class ShareExtensionBackGroundView: UIView { - - // MARK: - Properties - - weak var delegate: ShareExtensionBackGroundViewDelegate? - - // MARK: - UI Components - - lazy var label: UILabel = { - let label = UILabel() - label.text = "이 링크를 iBox 앱에서 여시겠습니까?" - label.font = .systemFont(ofSize: 15) - label.textColor = .label - return label - }() - - lazy var cancelButton: UIButton = { - let button = UIButton() - button.configuration = .plain() - button.configuration?.attributedTitle = .init( - "Cancel", - attributes: .init([.font: UIFont.systemFont(ofSize: 13)]) - ) - return button - }() - - lazy var openAppButton: UIButton = { - let button = UIButton() - button.configuration = .plain() - button.configuration?.attributedTitle = .init( - "Open", - attributes: .init([.font: UIFont.boldSystemFont(ofSize: 13)]) - ) - return button - }() - - // MARK: - Initializer - - override init(frame: CGRect) { - super.init(frame: frame) - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .systemBackground - clipsToBounds = true - layer.cornerRadius = 15 - - cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside) - openAppButton.addTarget(self, action: #selector(openAppButtonTapped), for: .touchUpInside) - } - - private func setupHierarchy() { - addSubview(label) - addSubview(cancelButton) - addSubview(openAppButton) - } - - private func setupLayout() { - label.snp.makeConstraints { make in - make.top.equalToSuperview().inset(25) - make.leading.equalToSuperview().inset(20) - } - - cancelButton.snp.makeConstraints { make in - make.trailing.equalTo(openAppButton.snp.leading) - make.centerY.equalTo(openAppButton.snp.centerY) - } - - openAppButton.snp.makeConstraints { make in - make.trailing.bottom.equalToSuperview().inset(15) - } - } - - // MARK: - Action Functions - - @objc func cancelButtonTapped() { - delegate?.didTapCancel() - } - - @objc func openAppButtonTapped() { - delegate?.didTapOpenApp() - } -} diff --git a/iBox/Sources/BoxList/AddBookmark/AddBookmarkView.swift b/iBox/Sources/AddBookmark/AddBookmarkView.swift similarity index 88% rename from iBox/Sources/BoxList/AddBookmark/AddBookmarkView.swift rename to iBox/Sources/AddBookmark/AddBookmarkView.swift index 9609862..31d488b 100644 --- a/iBox/Sources/BoxList/AddBookmark/AddBookmarkView.swift +++ b/iBox/Sources/AddBookmark/AddBookmarkView.swift @@ -1,16 +1,19 @@ // -// AddBookmarkBottomSheetView.swift +// AddBookmarkView.swift // iBox // // Created by jiyeon on 1/5/24. // import UIKit +import Combine import SnapKit class AddBookmarkView: UIView { + var cancellables = Set() + var onButtonTapped: (() -> Void)? var onTextChange: ((Bool) -> Void)? @@ -19,9 +22,9 @@ class AddBookmarkView: UIView { selectedFolderLabel.text = selectedFolderName } } - + // MARK: - UI Components - + private let textFieldView: UIView = UIView().then { $0.backgroundColor = UIColor.backgroundColor $0.layer.cornerRadius = 20 @@ -107,17 +110,22 @@ class AddBookmarkView: UIView { setupProperty() setupHierarchy() setupLayout() + setupBindings() } required init?(coder: NSCoder) { super.init(coder: coder) } + deinit { + AddBookmarkManager.shared.incomingTitle = nil + AddBookmarkManager.shared.incomingData = nil + } + // MARK: - Setup Methods private func setupProperty() { backgroundColor = .systemGroupedBackground - updateTextFieldWithIncomingData() clearButton.addTarget(self, action: #selector(clearTextView), for: .touchUpInside) button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) nameTextView.delegate = self @@ -210,28 +218,25 @@ class AddBookmarkView: UIView { } - private func updateTextField(textField: UITextView, placeholder: UILabel, withData data: String?) { - if let data = data, !data.isEmpty { - textField.text = data - placeholder.isHidden = true - if textField == nameTextView { - clearButton.isHidden = false + private func setupBindings() { + AddBookmarkManager.shared.$incomingTitle + .receive(on: DispatchQueue.main) + .sink { [weak self] title in + self?.nameTextView.text = title + self?.nameTextViewPlaceHolder.isHidden = !(title?.isEmpty ?? true) + self?.clearButton.isHidden = title?.isEmpty ?? true + self?.updateTextFieldsFilledState() } - } else { - textField.text = "" - placeholder.isHidden = false - if textField == nameTextView { - clearButton.isHidden = true - } - } - } - - private func updateTextFieldWithIncomingData() { - updateTextField(textField: nameTextView, placeholder: nameTextViewPlaceHolder, withData: URLDataManager.shared.incomingTitle) - URLDataManager.shared.incomingTitle = nil + .store(in: &cancellables) - updateTextField(textField: urlTextView, placeholder: urlTextViewPlaceHolder, withData: URLDataManager.shared.incomingData) - URLDataManager.shared.incomingData = nil + AddBookmarkManager.shared.$incomingData + .receive(on: DispatchQueue.main) + .sink { [weak self] url in + self?.urlTextView.text = url + self?.urlTextViewPlaceHolder.isHidden = !(url?.isEmpty ?? true) + self?.updateTextFieldsFilledState() + } + .store(in: &cancellables) } func updateTextFieldsFilledState() { @@ -280,7 +285,7 @@ extension AddBookmarkView: UITextViewDelegate { nameTextViewPlaceHolder.isHidden = !nameTextView.text.isEmpty clearButton.isHidden = nameTextView.text.isEmpty } - + if textView == urlTextView { urlTextViewPlaceHolder.isHidden = !urlTextView.text.isEmpty } diff --git a/iBox/Sources/BoxList/AddBookmark/AddBookmarkViewController.swift b/iBox/Sources/AddBookmark/AddBookmarkViewController.swift similarity index 98% rename from iBox/Sources/BoxList/AddBookmark/AddBookmarkViewController.swift rename to iBox/Sources/AddBookmark/AddBookmarkViewController.swift index a7154d0..0fa2ad8 100644 --- a/iBox/Sources/BoxList/AddBookmark/AddBookmarkViewController.swift +++ b/iBox/Sources/AddBookmark/AddBookmarkViewController.swift @@ -20,7 +20,7 @@ final class AddBookmarkViewController: UIViewController { var selectedFolderIndex: Int? var folders = [Folder]() - private let addBookmarkView = AddBookmarkView() + let addBookmarkView = AddBookmarkView() override func loadView() { super.loadView() @@ -31,6 +31,7 @@ final class AddBookmarkViewController: UIViewController { super.viewWillAppear(animated) updateSelectedFolder() addBookmarkView.updateTextFieldsFilledState() + addBookmarkView.nameTextView.becomeFirstResponder() } override func viewDidLoad() { diff --git a/iBox/Sources/BoxList/AddBookmark/FolderListCell.swift b/iBox/Sources/AddBookmark/FolderListCell.swift similarity index 100% rename from iBox/Sources/BoxList/AddBookmark/FolderListCell.swift rename to iBox/Sources/AddBookmark/FolderListCell.swift diff --git a/iBox/Sources/BoxList/AddBookmark/FolderListView.swift b/iBox/Sources/AddBookmark/FolderListView.swift similarity index 100% rename from iBox/Sources/BoxList/AddBookmark/FolderListView.swift rename to iBox/Sources/AddBookmark/FolderListView.swift diff --git a/iBox/Sources/BoxList/AddBookmark/FolderListViewController.swift b/iBox/Sources/AddBookmark/FolderListViewController.swift similarity index 100% rename from iBox/Sources/BoxList/AddBookmark/FolderListViewController.swift rename to iBox/Sources/AddBookmark/FolderListViewController.swift diff --git a/iBox/Sources/BoxList/BoxListViewController.swift b/iBox/Sources/BoxList/BoxListViewController.swift index 57aa367..74d81cb 100644 --- a/iBox/Sources/BoxList/BoxListViewController.swift +++ b/iBox/Sources/BoxList/BoxListViewController.swift @@ -13,7 +13,7 @@ class BoxListViewController: BaseViewController, BaseViewController didSet { if shouldPresentModalAutomatically { // shouldPresentModalAutomatically가 true로 설정될 때 함수 호출 - dismiss(animated: false) { + if findAddBookmarkView() == nil { self.addButtonTapped() } // 함수 호출 후 shouldPresentModalAutomatically를 false로 설정 diff --git a/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenViewController.swift b/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenViewController.swift index 935d34d..0e80413 100644 --- a/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenViewController.swift +++ b/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenViewController.swift @@ -80,8 +80,9 @@ class CustomLaunchScreenViewController: UIViewController { if let urlContext = self.urlContext, let tabBarController = window.rootViewController as? UITabBarController { - URLDataManager.shared.navigateToAddBookmarkView(from: urlContext.url, in: tabBarController) + AddBookmarkManager.shared.navigateToAddBookmarkView(from: urlContext.url, in: tabBarController) } + UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: {}, completion: nil) } } diff --git a/iBox/Sources/Extension/UIViewController+Extension.swift b/iBox/Sources/Extension/UIViewController+Extension.swift new file mode 100644 index 0000000..5277a9d --- /dev/null +++ b/iBox/Sources/Extension/UIViewController+Extension.swift @@ -0,0 +1,34 @@ +// +// UIViewController+Extension.swift +// iBox +// +// Created by Chan on 4/16/24. +// + +import UIKit + +extension UIViewController { + func findMainTabBarController() -> UITabBarController? { + var responder: UIResponder? = self + while let nextResponder = responder?.next { + if let viewController = nextResponder as? UITabBarController { + return viewController + } + responder = nextResponder + } + return nil + } + + func findAddBookmarkView() -> Bool? { + var responder: UIResponder? = self + while let nextResponder = responder?.next { + if let viewController = nextResponder as? AddBookmarkView { + return true + } + responder = nextResponder + } + return nil + } + + +} diff --git a/ShareExtension/Model/Metadata.swift b/iBox/Sources/Model/Metadata.swift similarity index 100% rename from ShareExtension/Model/Metadata.swift rename to iBox/Sources/Model/Metadata.swift diff --git a/iBox/Sources/SceneDelegate.swift b/iBox/Sources/SceneDelegate.swift index c3b0c18..42915c7 100644 --- a/iBox/Sources/SceneDelegate.swift +++ b/iBox/Sources/SceneDelegate.swift @@ -49,7 +49,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { if let urlContext = URLContexts.first, let tabBarController = window?.rootViewController as? UITabBarController { - URLDataManager.shared.navigateToAddBookmarkView(from: urlContext.url, in: tabBarController) + AddBookmarkManager.shared.navigateToAddBookmarkView(from: urlContext.url, in: tabBarController) } } diff --git a/iBox/Sources/Shared/AddBookmarkManager.swift b/iBox/Sources/Shared/AddBookmarkManager.swift new file mode 100644 index 0000000..68ce206 --- /dev/null +++ b/iBox/Sources/Shared/AddBookmarkManager.swift @@ -0,0 +1,83 @@ +// +// URLDataManager.swift +// iBox +// +// Created by 최종원 on 3/5/24. +// + +import UIKit + +import SwiftSoup + +class AddBookmarkManager { + static let shared = AddBookmarkManager() + + @Published var incomingTitle: String? + @Published var incomingData: String? + @Published var incomingFaviconUrl: String? + + private init() {} + + private func update(with data: (title: String?, data: String?, faviconUrl: String?)) { + DispatchQueue.main.async { + self.incomingTitle = data.title + self.incomingData = data.data + self.incomingFaviconUrl = data.faviconUrl + } + } + + private func parseHTML(_ html: String, _ url: URL) { + do { + let doc = try SwiftSoup.parse(html) + let title = try doc.title() + let faviconLink = try doc.select("link[rel='icon']").first()?.attr("href") + + self.update(with: (title: title, data: url.absoluteString, faviconUrl: faviconLink)) + + } catch { + print("Error parsing HTML: \(error)") + } + } + + private func extractDataParameter(from url: URL) -> String? { + guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false), + let queryItems = components.queryItems else { + return nil + } + return queryItems.first { $0.name == "data" }?.value + } + + private func fetchWebsiteDetails(from url: URL) { + let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in + guard let data = data, error == nil, + let html = String(data: data, encoding: .utf8) else { + print("Error downloading HTML: \(String(describing: error))") + return + } + + self?.parseHTML(html, url) + } + task.resume() + } + + func navigateToAddBookmarkView(from url: URL, in tabBarController: UITabBarController) { + guard url.scheme == "iBox", let urlString = extractDataParameter(from: url) else { return } + guard let url = URL(string: urlString) else { + print("Invalid URL") + return + } + + fetchWebsiteDetails(from: url) + + tabBarController.selectedIndex = 0 + + DispatchQueue.main.async { + guard let navigationController = tabBarController.selectedViewController as? UINavigationController, + let boxListViewController = navigationController.viewControllers.first as? BoxListViewController else { + return + } + boxListViewController.shouldPresentModalAutomatically = true + } + } + +} diff --git a/iBox/Sources/Shared/URLDataManager.swift b/iBox/Sources/Shared/URLDataManager.swift deleted file mode 100644 index 2f535ab..0000000 --- a/iBox/Sources/Shared/URLDataManager.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// URLDataManager.swift -// iBox -// -// Created by 최종원 on 3/5/24. -// - -import UIKit - -class URLDataManager { - static let shared = URLDataManager() - - var incomingTitle: String? - var incomingData: String? - var incomingFaviconUrl: String? - - private init() {} - - func update(with data: (title: String?, data: String?, faviconUrl: String?)) { - incomingTitle = data.title - incomingData = data.data - incomingFaviconUrl = data.faviconUrl - } - - func navigateToAddBookmarkView(from url: URL, in tabBarController: UITabBarController) { - guard url.scheme == "iBox" else { return } - - let urlData = URLdecoder.handleCustomURL(url) - self.update(with: urlData) - - tabBarController.selectedIndex = 0 - DispatchQueue.main.async { - guard let navigationController = tabBarController.selectedViewController as? UINavigationController, - let boxListViewController = navigationController.viewControllers.first as? BoxListViewController else { - return - } - boxListViewController.shouldPresentModalAutomatically = true - } - } -} diff --git a/iBox/Sources/Shared/URLdecoder.swift b/iBox/Sources/Shared/URLdecoder.swift deleted file mode 100644 index 7efb229..0000000 --- a/iBox/Sources/Shared/URLdecoder.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// URLdecoder.swift -// iBoxShareExtension -// -// Created by 김찬희 on 2024/03/14. -// - -import Foundation - -class URLdecoder { - static func handleCustomURL(_ url: URL) -> (title: String?, data: String?, faviconUrl: String?) { - guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return (nil, nil, nil) } - - let title = urlComponents.queryItems?.first(where: { $0.name == "title" })?.value - let data = urlComponents.queryItems?.first(where: { $0.name == "data" })?.value - let faviconUrl = urlComponents.queryItems?.first(where: { $0.name == "faviconUrl" })?.value - - let finalTitle: String? - if let title = title, !title.isEmpty { - finalTitle = title - } else if let data = data, let dataUrl = URL(string: data) { - finalTitle = dataUrl.host ?? "No Title" - } else { - finalTitle = nil - } - - return (finalTitle, data, faviconUrl) - } -} diff --git a/iBox/Sources/Web/WebViewController.swift b/iBox/Sources/Web/WebViewController.swift index 19b2ebe..5f017ea 100644 --- a/iBox/Sources/Web/WebViewController.swift +++ b/iBox/Sources/Web/WebViewController.swift @@ -46,14 +46,14 @@ class WebViewController: BaseViewController, BaseViewControllerProtocol extension WebViewController: WebViewDelegate { func pushAddBookMarkViewController(url: URL) { - URLDataManager.shared.incomingData = url.absoluteString + AddBookmarkManager.shared.incomingData = url.absoluteString - let addBookmarkViewController = AddBookmarkViewController() - addBookmarkViewController.delegate = delegate + if let iBoxUrl = URL(string: "iBox://url?data=" + url.absoluteString) { + if let tabBarController = findMainTabBarController() { + AddBookmarkManager.shared.navigateToAddBookmarkView(from: iBoxUrl, in: tabBarController) + } + } - let navigationController = UINavigationController(rootViewController: addBookmarkViewController) - navigationController.modalPresentationStyle = .pageSheet - self.present(navigationController, animated: true, completion: nil) } }