diff --git a/Project.swift b/Project.swift index dabac2a..4fa1bf0 100644 --- a/Project.swift +++ b/Project.swift @@ -19,6 +19,7 @@ class iBoxFactory: ProjectFactory { let dependencies: [TargetDependency] = [ .external(name: "SnapKit"), .external(name: "SwiftSoup"), + .external(name: "SkeletonView"), .target(name: "iBoxShareExtension") ] diff --git a/Tuist/Dependencies.swift b/Tuist/Dependencies.swift index 98b323e..0e66bfc 100644 --- a/Tuist/Dependencies.swift +++ b/Tuist/Dependencies.swift @@ -10,7 +10,8 @@ import ProjectDescription let spm = SwiftPackageManagerDependencies([ .remote(url: "https://github.com/SnapKit/SnapKit.git", requirement: .upToNextMinor(from: "5.0.1")), .remote(url: "https://github.com/scinfu/SwiftSoup.git", requirement: .upToNextMajor(from: "2.7.1")), -], productTypes: ["SnapKit": .framework, "SwiftSoup": .framework] + .remote(url: "https://github.com/Juanpe/SkeletonView.git", requirement: .upToNextMajor(from: "1.0.0")) +], productTypes: ["SnapKit": .framework, "SwiftSoup": .framework, "SkeletonView": .framework] ) let dependencies = Dependencies( diff --git a/iBox/Sources/AddBookmark/AddBookmarkView.swift b/iBox/Sources/AddBookmark/AddBookmarkView.swift index 085283a..e8d7d99 100644 --- a/iBox/Sources/AddBookmark/AddBookmarkView.swift +++ b/iBox/Sources/AddBookmark/AddBookmarkView.swift @@ -8,6 +8,7 @@ import UIKit import Combine +import SkeletonView import SnapKit class AddBookmarkView: UIView { @@ -35,6 +36,8 @@ class AddBookmarkView: UIView { $0.text = "북마크 이름" $0.font = .cellTitleFont $0.textColor = .systemGray3 + $0.isSkeletonable = true + $0.isHiddenWhenSkeletonIsActive = true } let nameTextView = UITextView().then { @@ -46,6 +49,9 @@ class AddBookmarkView: UIView { $0.isScrollEnabled = true $0.keyboardType = .default $0.autocorrectionType = .no + $0.isSkeletonable = true + $0.skeletonTextLineHeight = .fixed(20) + $0.skeletonPaddingInsets = .init(top: 5, left: 0, bottom: 5, right: 0) } private let clearButton = UIButton().then { @@ -62,6 +68,8 @@ class AddBookmarkView: UIView { $0.text = "URL" $0.font = .cellTitleFont $0.textColor = .systemGray3 + $0.isSkeletonable = true + $0.isHiddenWhenSkeletonIsActive = true } let urlTextView = UITextView().then { @@ -73,6 +81,10 @@ class AddBookmarkView: UIView { $0.isScrollEnabled = true $0.keyboardType = .URL $0.autocorrectionType = .no + $0.isSkeletonable = true + $0.skeletonTextLineHeight = .fixed(20) + $0.skeletonTextNumberOfLines = 2 + $0.skeletonPaddingInsets = .init(top: 5, left: 0, bottom: 5, right: 0) } private let button = UIButton(type: .custom).then { @@ -120,6 +132,8 @@ class AddBookmarkView: UIView { deinit { AddBookmarkManager.shared.incomingTitle = nil AddBookmarkManager.shared.incomingData = nil + AddBookmarkManager.shared.incomingFaviconUrl = nil + AddBookmarkManager.shared.incomingError = nil } // MARK: - Setup Methods @@ -130,6 +144,8 @@ class AddBookmarkView: UIView { button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) nameTextView.delegate = self urlTextView.delegate = self + + isSkeletonable = true } private func setupHierarchy() { diff --git a/iBox/Sources/AddBookmark/AddBookmarkViewController.swift b/iBox/Sources/AddBookmark/AddBookmarkViewController.swift index 363850a..671572a 100644 --- a/iBox/Sources/AddBookmark/AddBookmarkViewController.swift +++ b/iBox/Sources/AddBookmark/AddBookmarkViewController.swift @@ -5,8 +5,11 @@ // Created by jiyeon on 1/5/24. // +import Combine import UIKit +import SkeletonView + protocol AddBookmarkViewControllerProtocol: AnyObject { func addFolderDirect(_ folder: Folder) func addBookmarkDirect(_ bookmark: Bookmark, at folderIndex: Int) @@ -15,6 +18,8 @@ protocol AddBookmarkViewControllerProtocol: AnyObject { final class AddBookmarkViewController: UIViewController { weak var delegate: AddBookmarkViewControllerProtocol? + var cancellables = Set() + var haveValidInput = false var selectedFolder: Folder? var selectedFolderIndex: Int? @@ -38,6 +43,9 @@ final class AddBookmarkViewController: UIViewController { setupNavigationBar() updateSelectedFolder() addBookmarkView.nameTextView.becomeFirstResponder() + setupBindings() + + view.isSkeletonable = true } private func setupNavigationBar() { @@ -105,6 +113,33 @@ final class AddBookmarkViewController: UIViewController { } } + private func setupBindings() { + AddBookmarkManager.shared.$isFetching + .receive(on: DispatchQueue.main) + .sink { [weak self] isFetching in + if isFetching { + self?.view.hideSkeleton() + self?.view.showAnimatedGradientSkeleton() + } else { + self?.view.hideSkeleton() + } + } + .store(in: &cancellables) + + AddBookmarkManager.shared.$incomingError + .receive(on: DispatchQueue.main) + .sink { [weak self] error in + guard error != nil else { return } + let alert = UIAlertController(title: "오류", message: "해당 URL을 가져올 수 없습니다", preferredStyle: .alert) + let okAction = UIAlertAction(title: "확인", style: .default) { _ in + AddBookmarkManager.shared.isFetching = false + } + alert.addAction(okAction) + self?.present(alert, animated: true) + } + .store(in: &cancellables) + } + @objc private func cancelButtonTapped() { let isTextFieldsEmpty = addBookmarkView.nameTextView.text?.isEmpty ?? true && addBookmarkView.urlTextView.text?.isEmpty ?? true diff --git a/iBox/Sources/BoxList/BoxListViewController.swift b/iBox/Sources/BoxList/BoxListViewController.swift index 5acb884..87ba714 100644 --- a/iBox/Sources/BoxList/BoxListViewController.swift +++ b/iBox/Sources/BoxList/BoxListViewController.swift @@ -7,12 +7,18 @@ import UIKit +import SkeletonView + class BoxListViewController: BaseViewController, BaseViewControllerProtocol { var shouldPresentModalAutomatically: Bool = false { didSet { if shouldPresentModalAutomatically { - if findAddBookmarkViewController() == false { + if let vc = findAddBookmarkViewController() { + if vc.presentedViewController is UIAlertController { + vc.dismiss(animated: false) + } + } else { dismiss(animated: false) self.addButtonTapped() } diff --git a/iBox/Sources/Extension/UIViewController+Extension.swift b/iBox/Sources/Extension/UIViewController+Extension.swift index 51b6343..ff81455 100644 --- a/iBox/Sources/Extension/UIViewController+Extension.swift +++ b/iBox/Sources/Extension/UIViewController+Extension.swift @@ -19,11 +19,11 @@ extension UIViewController { return nil } - func findAddBookmarkViewController() -> Bool { + func findAddBookmarkViewController() -> AddBookmarkViewController? { if let navigationController = presentedViewController as? UINavigationController, - let _ = navigationController.topViewController as? AddBookmarkViewController { - return true + let vc = navigationController.topViewController as? AddBookmarkViewController { + return vc } - return false + return nil } } diff --git a/iBox/Sources/Model/BookmarkError.swift b/iBox/Sources/Model/BookmarkError.swift new file mode 100644 index 0000000..0128173 --- /dev/null +++ b/iBox/Sources/Model/BookmarkError.swift @@ -0,0 +1,14 @@ +// +// BookmarkError.swift +// iBox +// +// Created by 이지현 on 4/21/24. +// + +import Foundation + +enum BookmarkError { + case htmlError + case decodeError + case parseError +} diff --git a/iBox/Sources/Shared/AddBookmarkManager.swift b/iBox/Sources/Shared/AddBookmarkManager.swift index 1b0962d..e1f8096 100644 --- a/iBox/Sources/Shared/AddBookmarkManager.swift +++ b/iBox/Sources/Shared/AddBookmarkManager.swift @@ -12,14 +12,17 @@ import SwiftSoup class AddBookmarkManager { static let shared = AddBookmarkManager() + @Published var isFetching: Bool = false @Published var incomingTitle: String? @Published var incomingData: String? @Published var incomingFaviconUrl: String? + @Published var incomingError: BookmarkError? private init() {} private func update(with data: (title: String?, data: String?, faviconUrl: String?)) { DispatchQueue.main.async { + self.isFetching = false self.incomingTitle = data.title?.removingPercentEncoding self.incomingData = data.data?.removingPercentEncoding self.incomingFaviconUrl = data.faviconUrl?.removingPercentEncoding @@ -33,9 +36,8 @@ class AddBookmarkManager { 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)") + self.incomingError = .parseError } } @@ -53,7 +55,7 @@ class AddBookmarkManager { 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 else { - print("Error downloading HTML: \(String(describing: error))") + self?.incomingError = .htmlError return } @@ -61,7 +63,7 @@ class AddBookmarkManager { let encoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString))) guard let html = String(data: data, encoding: encoding) else { - print("Failed to decode data with encoding: \(encodingName)") + self?.incomingError = .decodeError return } @@ -72,11 +74,12 @@ class AddBookmarkManager { 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 \(urlString)") - return - } + guard let url = URL(string: urlString) else { return } + incomingTitle = nil + incomingData = nil + incomingFaviconUrl = nil + isFetching = true fetchWebsiteDetails(from: url) tabBarController.selectedIndex = 0