diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index a8aee27..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,10 +0,0 @@ -### πŸ“Œ κ°œμš” -- - -### πŸ’» μž‘μ—… λ‚΄μš© -- - -### πŸ–ΌοΈ μŠ€ν¬λ¦°μƒ· -||| -|---|---| -||| diff --git a/.github/Release-note.yml b/.github/Release-note.yml deleted file mode 100644 index e9c7fa8..0000000 --- a/.github/Release-note.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Author by chanhihi -# Date 2023.08.09 -# name-template: "v$NEXT_MINOR_VERSION 🦊" -# tag-template: "v$NEXT_MINOR_VERSION" - -name-template: "v$RESOLVED_VERSION 🦊" -tag-template: "v$RESOLVED_VERSION" -version-resolver: - major: - labels: - - "⚠️ Breaking changes" - minor: - labels: - - "✨ Enhancement" - patch: - labels: - - "βš’ Refactor" - - "🐞 Bug" - default: patch - -categories: - - title: "⚠️ Breaking changes" - labels: - - "⚠️ Breaking Change" - - title: "πŸš€ Features" - labels: - - "✨ Enhancement" - - "βš’ Refactor" - - "πŸ› Structure" - - title: "πŸ› Bug Fixes" - labels: - - "🐞 Bug" - - title: "πŸ“š Documentation" - labels: - - "πŸ“„ Documentation" - - title: "🎨 Style" - labels: - - "πŸ’„ UI/UX" - - title: "πŸ— Infrastructure" - labels: - - "🌐 DevOps" -exclude-labels: - - "πŸ’– Question" - - "β˜‚οΈ Umbrella" - -change-template: "- $TITLE (#$NUMBER)" -change-title-escapes: '\<*_&' - -template: | - ## Changes - $CHANGES diff --git a/.github/workflows/Deployment.yml b/.github/workflows/Deployment.yml deleted file mode 100644 index 49762e0..0000000 --- a/.github/workflows/Deployment.yml +++ /dev/null @@ -1,98 +0,0 @@ -# Author by chanhihi -# Date 2024.04.26 - -name: Deployment - -on: - pull_request: - branches: - - main - types: - - closed - -jobs: - build: - name: Deploy on macOS latest - Release for iOS - runs-on: macos-latest - env: - XCODE_VERSION: "15.2.0" - SWIFT_VERSION: "5.9.2" - RUBY_VERSION: "2.6.10" - TUIST_VERSION: "3.36.2" - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up Ruby 2.6 - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ env.RUBY_VERSION }} - bundler-cache: true - - - name: Set Xcode version - run: sudo xcode-select -s '/Applications/Xcode_15.2.0.app/Contents/Developer' - - - name: Setup Swift - uses: swift-actions/setup-swift@v1 - with: - swift-version: ${{ env.SWIFT_VERSION }} - - - name: .env - run: touch .env && - echo "APP_STORE_CONNECT_API_KEY_KEY_ID=${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }}" >> .env && - echo "APP_STORE_CONNECT_API_KEY_ISSUER_ID=${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}" >> .env && - echo "APP_STORE_CONNECT_API_KEY_KEY=${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }}" >> .env - - - name: Setting Master Key - run: | - echo "$MASTER_KEY" > Tuist/master.key - env: - MASTER_KEY: ${{secrets.MASTER_KEY}} - - - name: Mise - uses: jdx/mise-action@v2 - - - name: Install Tuist - run: mise install tuist@${{ env.TUIST_VERSION }} - - - name: Tuist version - run: mise use -g tuist@${{ env.TUIST_VERSION }} - - - name: Install Fastlane - run: brew install fastlane - - - name: Tuist clean - run: tuist clean - - - name: Tuist fetch - run: tuist fetch - - - name: Tuist Signing Decrypt - run: tuist signing decrypt - - - name: Set Keychain - run: fastlane set_keychain - env: - KEYCHAIN_NAME: ${{ secrets.KEYCHAIN_NAME }} - KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} - - - name: Generate Xcode project with Tuist - run: tuist generate - - - name: Fastlane run - run: fastlane tf - - - name: Tagging - id: tag_version - uses: mathieudutour/github-tag-action@v6.1 - with: - github_token: ${{ secrets.CHANHIHI }} - - - name: Draft Release - id: draft_release - uses: release-drafter/release-drafter@v5 - with: - config-name: Release-note.yml - env: - GITHUB_TOKEN: ${{ secrets.CHANHIHI }} diff --git a/.gitignore b/.gitignore index 0d5c680..551a3f5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,13 +3,6 @@ .DS_Store .AppleDouble .LSOverride -.env - -# Auth -*.key -*.p8 -*.p12 -*.cer # Icon must end with two Icon @@ -75,5 +68,3 @@ Derived/ ### Tuist managed dependencies ### Tuist/Dependencies - -.mise.toml diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 46c791e..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "iBox/Resources/Version"] - path = iBox/Resources/Version - url = https://github.com/42Box/versioning diff --git a/Gemfile b/Gemfile deleted file mode 100644 index f0e202f..0000000 --- a/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source "https://rubygems.org" - -gem "dotenv" -gem "fastlane" diff --git a/Makefile b/Makefile deleted file mode 100644 index e8ffb27..0000000 --- a/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -gen: - tuist fetch - tuist generate - -clean: - tuist clean - -fclean: clean - find . -name "*.xcodeproj" -exec rm -rf {} \; - find . -name "*.xcworkspace" -exec rm -rf {} \; - -re: fclean gen - -tf: - tuist signing decrypt - tuist fetch - tuist generate - - fastlane tf \ No newline at end of file diff --git a/Project.swift b/Project.swift index 7e20388..d32396e 100644 --- a/Project.swift +++ b/Project.swift @@ -14,24 +14,15 @@ protocol ProjectFactory { class iBoxFactory: ProjectFactory { let projectName: String = "iBox" let bundleId: String = "com.box42.iBox" - let iosVersion: String = "15.0" let dependencies: [TargetDependency] = [ - .external(name: "SnapKit"), - .external(name: "SwiftSoup"), - .external(name: "SkeletonView"), - .target(name: "iBoxShareExtension") - ] - - let iBoxShareExtensionDependencies: [TargetDependency] = [ .external(name: "SnapKit") ] - private let appInfoPlist: [String: Plist.Value] = [ + let infoPlist: [String: Plist.Value] = [ "ITSAppUsesNonExemptEncryption": false, - "CFBundleDisplayName": "iBox", "CFBundleName": "iBox", - "CFBundleShortVersionString": "1.0.0", + "CFBundleShortVersionString": "1.2.1", "CFBundleVersion": "1", "UILaunchStoryboardName": "LaunchScreen", "UIApplicationSceneManifest": [ @@ -45,72 +36,23 @@ class iBoxFactory: ProjectFactory { ] ] ], - "CFBundleURLTypes": [ - [ - "CFBundleURLName": "com.url.iBox", - "CFBundleURLSchemes": ["iBox"], - "CFBundleTypeRole": "Editor" - ] - ], - "NSAppTransportSecurity": [ - "NSAllowsArbitraryLoadsInWebContent": true - ] + "UIUserInterfaceStyle": "Light" ] - private let shareExtensionInfoPlist: [String: Plist.Value] = [ - "CFBundleDisplayName": "iBox.Share", - "CFBundleShortVersionString": "1.0.0", - "CFBundleVersion": "1", - "NSExtension": [ - "NSExtensionAttributes": [ - "NSExtensionActivationRule": [ - "NSExtensionActivationSupportsWebPageWithMaxCount": 1, - "NSExtensionActivationSupportsWebURLWithMaxCount": 1, - "SUBQUERY": [ - "extensionItems": [ - "SUBQUERY": [ - "attachments": [ - "ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO 'public.data'": "TRUE" - ], - "@count": 1 - ] - ], - "@count": 1 - ] - ] - ], - "NSExtensionPointIdentifier": "com.apple.share-services", - "NSExtensionPrincipalClass": "$(PRODUCT_MODULE_NAME).CustomShareViewController" - ] - ] - - func generateTarget() -> [ProjectDescription.Target] { - let appTarget = Target( + func generateTarget() -> [ProjectDescription.Target] {[ + Target( name: projectName, destinations: .iOS, product: .app, bundleId: bundleId, - deploymentTargets: .iOS(iosVersion), - infoPlist: .extendingDefault(with: appInfoPlist), + deploymentTargets: .iOS("15.0"), + infoPlist: .extendingDefault(with: infoPlist), sources: ["\(projectName)/Sources/**"], resources: "\(projectName)/Resources/**", dependencies: dependencies ) - - let shareExtensionTarget = Target( - name: "\(projectName)ShareExtension", - destinations: .iOS, - product: .appExtension, - bundleId: "\(bundleId).ShareExtension", - deploymentTargets: .iOS(iosVersion), - infoPlist: .extendingDefault(with: shareExtensionInfoPlist), - sources: ["ShareExtension/Sources/**"], - resources: ["ShareExtension/Resources/**"], - dependencies: iBoxShareExtensionDependencies - ) - - return [appTarget, shareExtensionTarget] - } + ]} + } diff --git a/README.md b/README.md deleted file mode 100644 index 4628625..0000000 --- a/README.md +++ /dev/null @@ -1,100 +0,0 @@ -
- Fox -
- - -[appstore](https://apps.apple.com) - -
- -# πŸ’β€β™€οΈπŸ’β€β™‚οΈ Introduction -μ•ˆλ…•ν•˜μ„Έμš”! 42Box iOS νŒ€μž…λ‹ˆλ‹€. 🦊 
-42BoxλŠ” μ—¬λŸ¬ μ›Ή 링크λ₯Ό μ‰½κ²Œ μ €μž₯ν•˜κ³  ν΄λ”λ³„λ‘œ 정리할 수 μžˆλŠ” μ•±μž…λ‹ˆλ‹€.Β 
-λ„μ„œκ΄€ μ„œλΉ„μŠ€, μΆœμž… 관리 λ“± 42μ„œμšΈ μƒν™œμ— ν•„μˆ˜μ μΈ μ„œλΉ„μŠ€λ“€μ„ κΈ°λ³Έ 폴더에 λͺ¨μ•„ μ œκ³΅ν•˜κ³  μžˆμ–΄μš”.Β 
-πŸ“’Β μ„œλΉ„μŠ€λ₯Ό μ›ν™œν•˜κ²Œ μ΄μš©ν•˜μ‹€ 수 μžˆλ„λ‘ 핡심 κΈ°λŠ₯을 μ†Œκ°œν•©λ‹ˆλ‹€. πŸ‘€Β 
- - -### ⭐️ Key Function -1. κΈ°λ³Έ 42폴더 제곡 - - > 42μ„œμšΈ μ„œλΉ„μŠ€μ™€ κ΄€λ ¨λœ 링크λ₯Ό 사전 μ €μž₯ν•œ μ „μš© 폴더λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. - -2. 뢁마크 관리 - - > μ›Ή 링크λ₯Ό μ†μ‰½κ²Œ μ €μž₯ν•˜κ³  λ‚˜λ§Œμ˜ ν΄λ”λ‘œ 정리할 수 μžˆμŠ΅λ‹ˆλ‹€. - -3. 즐겨찾기 - - > κ°€μž₯ 자주 λ°©λ¬Έν•˜λŠ” μ›Ή 링크에 λΉ λ₯΄κ²Œ μ ‘κ·Όν•˜κΈ° μœ„ν•΄ 즐겨찾기 νƒ­μœΌλ‘œ μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€. - -4. 곡유 - - > μ™ΈλΆ€ μ•±μ—μ„œ μ›Ή 링크λ₯Ό κ³΅μœ ν•΄ 뢁마크λ₯Ό μ‰½κ²Œ μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€. - -5. 링크 미리보기 - - > 링크λ₯Ό μ™„μ „νžˆ 열지 μ•Šκ³ λ„ 뢁마크λ₯Ό 길게 눌러 미리 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. - -6. 제슀처둜 뢁마크 μΆ”κ°€ - - > λΈŒλΌμš°μ§•ν•˜λŠ” λ™μ•ˆ κ°„λ‹¨ν•œ 제슀처둜 ν˜„μž¬ μ›Ή 링크λ₯Ό 뢁마크둜 μ‰½κ²Œ μ €μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€. - -7. ν…Œλ§ˆ 및 μ„€μ • - - > ν…Œλ§ˆμ™€ μ‹œμž‘ ν™”λ©΄ μ„€μ • λ“± 개인 λ§žμΆ€ν™” 섀정을 ν•  수 μžˆμŠ΅λ‹ˆλ‹€. - -### πŸ“· ScreenShot - -| 폴더 관리 | μ™ΈλΆ€ μ•±μ—μ„œ 곡유 | 제슀처둜 뢁마크 μΆ”κ°€ | -|:---:|:---:|:---:| -|image|image|image| - - -| 즐겨찾기 μ„€μ • | ν…Œλ§ˆ 및 μ„€μ • | -|:---:|:---:| -|image|image| - -
-
- -# βš™οΈ Development Environment -![iOS badge](https://img.shields.io/badge/iOS-15.0+-silver?style=flat-square) -![iOS badge](https://img.shields.io/badge/Xcode-15.0+-blue?style=flat-square) - -### πŸ›  Skills & Tech Stack - -* UIKit -* Tuist -* Combine -* MVVM -* Webkit -* ShareExtension - - -### πŸ“š Library -| Name |Version | -| ----------------- | ------ | -| SnapKit | `5.0.1`| -| SwiftSoup | `2.7.1`| -| SkeletonView | `1.0.0`| - -
-
- -# πŸ‘©πŸ»β€πŸ’»πŸ§‘πŸ»β€πŸ’» Contributor - - - - - - - - - - - - - - - -
κ΅¬μ§€μ—°κΉ€μ°¬ν¬μ΄μ§€ν˜„μ΅œμ’…μ›

jikoo


chanheki


jihyeole


jonchoi

diff --git a/ShareExtension/Resources/Media.xcassets/128.imageset/128 1.png b/ShareExtension/Resources/Media.xcassets/128.imageset/128 1.png deleted file mode 100644 index 077d76c..0000000 Binary files a/ShareExtension/Resources/Media.xcassets/128.imageset/128 1.png and /dev/null differ diff --git a/ShareExtension/Resources/Media.xcassets/128.imageset/128.png b/ShareExtension/Resources/Media.xcassets/128.imageset/128.png deleted file mode 100644 index 077d76c..0000000 Binary files a/ShareExtension/Resources/Media.xcassets/128.imageset/128.png and /dev/null differ diff --git a/ShareExtension/Resources/Media.xcassets/128.imageset/Contents.json b/ShareExtension/Resources/Media.xcassets/128.imageset/Contents.json deleted file mode 100644 index 8b99860..0000000 --- a/ShareExtension/Resources/Media.xcassets/128.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "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 deleted file mode 100644 index 73c0059..0000000 --- a/ShareExtension/Resources/Media.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ShareExtension/Sources/Extension/ShareExtension+UIColor+Extension.swift b/ShareExtension/Sources/Extension/ShareExtension+UIColor+Extension.swift deleted file mode 100644 index 817e21e..0000000 --- a/ShareExtension/Sources/Extension/ShareExtension+UIColor+Extension.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// 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 deleted file mode 100644 index 466fe44..0000000 --- a/ShareExtension/Sources/Extension/ShareExtension+UIbutton+Extension.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// 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 deleted file mode 100644 index e68ff61..0000000 --- a/ShareExtension/Sources/ShareViewController.swift +++ /dev/null @@ -1,187 +0,0 @@ -// -// ShareViewController.swift -// iBoxWebShareExtension -// -// Created by Chan on 2/8/24. -// - -import UIKit -import Social -import UniformTypeIdentifiers - -import SnapKit - -@objc(CustomShareViewController) -class CustomShareViewController: UIViewController { - - var dataURL: String? - var panelView = ShareExtensionPanelView() - var modalView: UIView = { - let modalview = UIView() - modalview.backgroundColor = .clear - return modalview - }() - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupProperty() - setupHierarchy() - setupLayout() - setupModal() - extractSharedURL() - } - - // MARK: - Setup Methods - - private func setupProperty() { - panelView.delegate = self - } - - private func setupHierarchy() { - view.addSubview(modalView) - modalView.addSubview(panelView) - } - - private func setupLayout() { - modalView.snp.makeConstraints { make in - make.edges.equalTo(view.safeAreaLayoutGuide) - } - - panelView.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 { - print("No extension items found.") - return - } - - if let item = extensionContext?.inputItems.first as? NSExtensionItem { - for attachment in item.attachments ?? [] { - if attachment.hasItemConformingToTypeIdentifier("public.plain-text") { - attachment.loadItem(forTypeIdentifier: "public.plain-text", options: nil) { (data, error) in - DispatchQueue.main.async { - if let text = data as? String { - self.extractURL(fromText: text) - } else { - print("Error loading text: \(String(describing: error))") - } - } - } - } - } - } - - 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 - } else { - print("Attachment does not conform to URL type.") - } - } - } - - private func extractURL(fromText text: String) { - let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) - let matches = detector?.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf8.count)) - - if let firstMatch = matches?.first, let range = Range(firstMatch.range, in: text), let url = URL(string: String(text[range])) { - print("Extracted URL: \(url)") - self.dataURL = url.absoluteString - } else { - print("No URL found in text") - } - } - - // 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 !panelView.frame.contains(location) { - cancel() - } - } -} - -extension CustomShareViewController: ShareExtensionPanelViewDelegate { - - func didTapCancel() { - cancel() - } - - func didTapOpenApp() { - guard let sharedURL = dataURL else { - print("Share extension error") - return - } - - let urlString = "iBox://url?data=\(sharedURL)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" - - if let openUrl = URL(string: urlString) { - if self.openURL(openUrl) { - print("iBox 앱이 μ„±κ³΅μ μœΌλ‘œ μ—΄λ ΈμŠ΅λ‹ˆλ‹€.") - } else { - print("iBox 앱을 μ—΄ 수 μ—†μŠ΅λ‹ˆλ‹€.") - } - } else { - print("url error") - // ν•΄λ‹Ή url은 μ‚¬μš©ν•  수 μ—†μŒμ„ λ³΄μ—¬μ£ΌλŠ” λ·°λ₯Ό λ§Œλ“€μ–΄μ•Όν•¨. - } - } -} - -extension CustomShareViewController: UIGestureRecognizerDelegate { - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return true - } -} diff --git a/ShareExtension/Sources/View/ShareExtensionPanelView.swift b/ShareExtension/Sources/View/ShareExtensionPanelView.swift deleted file mode 100644 index c84f702..0000000 --- a/ShareExtension/Sources/View/ShareExtensionPanelView.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// BackGroundView.swift -// iBox -// -// Created by Chan on 2/19/24. -// - -import UIKit - -import SnapKit - -protocol ShareExtensionPanelViewDelegate: AnyObject { - func didTapCancel() - func didTapOpenApp() -} - -class ShareExtensionPanelView: UIView { - - // MARK: - Properties - weak var delegate: ShareExtensionPanelViewDelegate? - - // 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(.label, 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/Tuist/Dependencies.swift b/Tuist/Dependencies.swift index 0e66bfc..b258631 100644 --- a/Tuist/Dependencies.swift +++ b/Tuist/Dependencies.swift @@ -8,11 +8,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")), - .remote(url: "https://github.com/Juanpe/SkeletonView.git", requirement: .upToNextMajor(from: "1.0.0")) -], productTypes: ["SnapKit": .framework, "SwiftSoup": .framework, "SkeletonView": .framework] -) + .remote(url: "https://github.com/SnapKit/SnapKit.git", requirement: .upToNextMinor(from: "5.0.1")) +]) let dependencies = Dependencies( swiftPackageManager: spm, diff --git a/Tuist/Signing/debug.cer.encrypted b/Tuist/Signing/debug.cer.encrypted deleted file mode 100644 index 30ca264..0000000 --- a/Tuist/Signing/debug.cer.encrypted +++ /dev/null @@ -1 +0,0 @@ -IIQiI4/q9SfKX3HQz+Il4A==-Zm/M7463yYLz0r3uVmfIOViANQzqd+o0Npp9PpW/mNLB8t26kilJbNnJFsvURmuT5rmcZ0LbUxnfK9c2vPuRRwqcTsznLL9SEW8CZHt4CH5pF07LrSvoNQqkfWiBCHO/4dLgIa6NoiVN8rOIsIQ9ghgYTgwItg427WTKX8cB7tiewG1oOUEtxJvj/ju4Aa+qV+xh3Vp96x0dJvPXO6DToJTEWwU1o3CNWk8j+ibLmPz/7UzOq3u6s1rGNjsmNyyoQ6jjQ3HyMtlaH18LfqwK2JKiMOny1hA4sL3B9sAkJl2r7cyxM7MLFMI/qg4kfNIHtYs5bTQ359uke29QwRnV7F6YcV+hfZSxMcxMUSkOvGaEk7Q4jhlAkDSt1YmvOC+pqs6E4LI8RktFPYd+Z2Kvb1aG2TOoRjSHLojJNKrlobOOIzmnlKesLj3YzfnfXfz6T5rssyRa9kfWrGkkdjSE4CbW0Tc/tt4t8zGp964b8fYaBh7iTFyKUhUuCZR9Rl+0Gd3aSwCETG+cjw/6IeGTF6FKlJygckhT0h17eI0IgxsRQoKjJnKj35Sqd8Etlp1b6dTsODi8FSAq7UM1OHOFcwBqMPqcpgSYkxzBuQ8cyH0AGs1H3UToWZ3KQ5Zdfc8lR0njnRHpqYpNdBJBClEXJbra80jtGGYRtoF51msE81j61HpKivYuG6G5MX/vf6He3R0u+fEXX0fcouwfGPR+rK6GcX3YbMKdHw0tZ01uODlp5WctdRfuiGNJU/VV34OmqmNygFbeXINfSqFgAyb06gXcQ6flmW01/skfdgt7CqM9lviK7mWrpwzk3qqMqPbxw+1KJ7/T56ct9u/kjsxx5GNG2hI/a3hVvZX8eZCyg1PV3WVbXP+Fw1C3RZeUvll0SwxsZzihlb3gC+dU7CXVdPkImDpC7EfJdJIzWF5NeTltvugAtpy0EYsGXlhzbk020rDonAO1vDxqh+2lTNKG0/00ISItDKZZloY3phXAQqxLidJ68gwmVN2P1SqD1Ynim0EvKqvWPba9HZI9vrsd6qBZtBibx3Dna5gp/fyGi8recNatuzyCXe8QjVpuRB0N3y9at0j9Fz37jY7K99G1h81XEpaWaLXXFeFTFluQsb23Q3MUdJsgpXEzjvmKgLak5ZFx1cQ8Js3TLOPUk9PgJBc5ghgkUcXsgWT2q4PvoKAO3r4Safng5vhTG4RapuxpUBWlRumCExPKsUQtM4vpwD5F8gsKlWahiziVO6fB7TcS83Wfdx44zCxu31Xn6zffedlUOqFNHEtpDOjjrMHBOfG2uXJG7Ff+jYUtE6Vsx/bx+FwCoPlQHedETMgGZx5XURuXBZ8VXv+z4kX9o9Ohu6RO8g2KY9DgLQhkM9XRJIJkW7lqVuG5CzA/+p5KfL9f84l5YbnMIheL9UkT427E8YXyaGkJiI26wv9dO2Og4inP+DMcsDJva0+ClcHWf8/9VnHWDPku3LTDQFSxaPhOPaobEtc5/kjjzDmCDn7ZE7hFQoiYtyXRKpmQ80Ez8Ji4IR0OAQl2Z2bcN+9rpBrCv0kyZJYGKK/o3QcdqZj5HNkRGBE4EOZiKvTniIY2Z7kNGxM5moZYqSWhU9/8pUtA9RUIX5E3mW4KGCJ2+uXaEl2b9H8ia+cQVUzzidoNXwWhGD2wlZPrKewTb8K+zapxsyUbWLrkvYAXRCvQunmVHmD+v4DaR/95J+8xjCxc/02nMUWAxmPJrptWTjs2MBlcTIGpQDMnFo9LJnOu4S2wbfHc9/cOGVTnX0i+3UaghRWDwpyB+/i9Ioy2sidLW2/R0yVJTa4cyDn0ECFNe3W/l26hd/nXQOCi+BSiRiE1x0SZVJ4NIJjhKvIeJRc7iiFe6UTcN9StH2Ik940qgiZEO2yKFVVCWwkfsz9pFzuw+pxP7viAnELn5C3xU3NT7nSw0GEd1d6h5P9yk22PxxFGEXj3UL5Ygx6HGg== \ No newline at end of file diff --git a/Tuist/Signing/debug.p12.encrypted b/Tuist/Signing/debug.p12.encrypted deleted file mode 100644 index df2043e..0000000 --- a/Tuist/Signing/debug.p12.encrypted +++ /dev/null @@ -1 +0,0 @@ -5OXOPxe34sNNBC4hMy9pxQ==-Kr6wbaB5ueQaIHZlZBV/sRaI/5uPrRtYBP3nlBjbOi5Ve/ogG85bQeCRJoONVcckTasaL6urhkJW7/3jLzq4vzK+deYA+fldHcon7NY2LUs/OYrn6fLthFsiZWGZwMaDNszINCbPJQTwUCu870gSJM927rs/q9tRnU8VGJTXthfC9vmvI6hHYiHFP7SM2eMFhGuClnvEYx99AZRBrfeWL319G0hXb6tDRQ8nfh1PQ9YUbs/C47InZl5aPiH2aGYqWwch0OD5+iwkta9n7YtbgMYCBH+lPHz8uvwshgzyvdevOfU4Z8ep1ALRYVVtd2X0H0EM86U4flB19mXu1c/lY/0nUcIVr7g24ydagTJqakq1QIKD6WxKCk2C+sgge3lZYPngbmWvHwmnEUuaeq4VU74KUFQw2FcZZWJhG81tDbHC0K5z+HYndShghFUxN9LPFxvrCOFO1nbB4jzrSSd8q8pqBkglM1bhGPAmb7/BUoaDaZtqI91V5wDnhfFuFZEXK3hzJwDirLHezBjCqFfUkYdtAL/6X9dW8Jakyqq5gF5zCX9nN61YK5ZmrTvixz4rDuOqbETTHJbOCpd5NfIWi/4VHIUHcrCrTHKEA1vtNVZnrQZLvjIiAyYOL1N5dBOmV8rqD6dVsi4g5qCgL2uzNVgIp9J0yBQuzeeHy7aaJXQcE0wTjwySMU/LnUlFlzUsyGTML8hLcn7FriVf8Zwpr3A70HtKN4LfL8F7vmJFp0iHDZxRVspii1PYfNkSMHFBEJyZx0TkpdwVLCgUFes8YTuIj3Cawfc7NNtviBf7Xy0iXCXmDdqIkJO8+9ELehnkdSdOb09EzzcajjEE5/faVL9BemcyMg0tNQFywONlKLKA0gN5N+IlBeoEukJSWT0izvOPSPETEwsi5hgBvStEh93CbHr9dkzfTQ8raITAvA58F8mfz7mMypSGLV9McxPHvWP7Dk/oWNKCuUjJ6GwyhU27Lb8oaD/uEpSQIHmc+3NLwQ7LbRI+m2kK4ZCSPRAuxhDLpEZW2bfg1ianSPePXZSBkv810P8SkbZeGyzUtx3f7/mcbtCcACvA44sn+m5bL5MDJsMPCVLGopBwCwUPmVEYrCtG0QpYv3yHgazUU4SqJZa8eVA+RsWrH0ZXM6fgEM57xBjH3qGDUwzMYrW4CqtWNdn7v5EGoCgPkTRQ4h2msVMNXPby2e8WeLA9RNAzBFliioykX23u1EyomXbVYxQOccoAnelyvVtwy2TGbznqT4ucNak0NoXMtlNu9ZZ9yjGr3LyFOhk89XeR0NoiUV7Kj6ATx7Q5lODigdmV+ImW0wqREqACJMQUdPbAZWMiivRwNMnpQduet4USo5qASV788F3uhggWZBlL4Tgmh79CveSbpasIdA/IOhHW9zV76/kAvDSyGx5ZKaegBRhroaUMUTysdsphVreNSteEMXCGADdUOYab5I8U196Kh8dzheXLH0LlAwu2+ZHRcGkYCZSDRlmQutSge1zWYJX51K5Nn8CalcTPijERDfjzOd+mr26ELev6DH/xrProeu5kH0w4sXlB5wIFD5t7P2bb+8Rrmx53qjJ5/zXmwVnHFfnhyEtTTF+spbPM6QveO+DMd//4JbNgccy/+EfAlxz1lNaSik1NV2tRwMKthP3gjlZCEzZHIoUfJF9sTEzEd5dcHNhPL8bcpY7G0klYZyxrkHkSo45QrHAg/Fr4UxQaaClIRYaJvjzI73+9K/i0pIcxNHOJ5QR3GrR12bXNAQ2Ey4hEhNgq1I/hygES8UeCPL2nZdcJuvdcx7nMrUGTEkm5v8TadTnv1hg3x+MKD0c/3gpn1GwuKWqW4eBgXX1DpN8C072OkeaKtqYWl7JfmZfV0iRJZoVJ2l/pAQ4XluVkVdegHSYn1YjbM8NQNUwWa6we7pChyb05Upr5kMCwrChHx6t/0kNd2CQO1gKq5oH2/j1gp0VD0XePWLftIgFGWEZwi/mLNlHHoKQxcRIMKh/KdvlrxmCQuNwhdAFhBfapKl3STjWG0DBMy1N+qxV3gTCMdOy25UfBL7Zllvxy94xoy1S1GkMnfq2UZHeQMFE8xqZbQ6p5WtE08ClTLGU5+1hgj3pdPBJsUGtQJg3Qnfsta2iSyJB/ivfqN49yAUgozOMPWfiaxy27VOeJWsmuWns3HTn+zjsUeyYFbR3y4kIbsJTtqCuY+Mcfax05jgJt/QuYVftXIrdvk6wQxQc+Uzvf+Yj+CXAuBJzvBf4Tj6gQu3GpMlX/Fe7Ab9Rn+o8N6esp2zDpy9klJEnnCP4SyR3+VgjXHjbi2U1/pT5pKfQJ10v0sqNNivK1fqlvxDY2ymqDtYWl/H8ppAxCG0BcZ6si7gADF0o7rWb3Z8SMQmKtjyIeyoFwswn+41BTQD2hUfKmMc22f2px/PxKSmvcbCu7hJbG0B3tP5KJF9U1kPlSU8ExTTeX2XRdhK010/3m1Q6WfWwyiJ7t4FTs/loU6lR9wkKjrUpyT3GnNrcFulfomXC4AGVEFi5tjEYiOLemuBuFO4w0aRb+QrHZ4tYsKawYM4qpD27Q5A5rSz+lPg6fNNWhaIKaEoIasI3MD7zUxVS6DX4FNBqFat+ikvCHb5vkM9OEL4YCf5NiDXAdUxh1LzSjR0EMJIuN0j8VHSpEJMqmX4Y4rXYtPwuA4qn7xYYV9bPEMCasG9FKeVs0vl8ATqxvYGEgZtf2qoUdcwNKOAJ2RwJYzdSZuwNFLfPImL12WDxr7uuuv7jtmXIxinNgm1mh0C9lBM+gNaLEkswJJb5kj/e9engopLMXG4RNscdP1Vg7UluL3JmRQUCftEAsLWAtKNcti3HkfAc9VueGAybqFc0mW8orM5UQV8H4k++0Rb0dJeYVkbiPgsH81Aqg9u/81vduW1T6ub7/5IIF5bXKNzyC2eVD6U9NveoTL/7eVHuVG2AA5ojCK7QOEPSHIs6dMIHuQnXA+y3JU4YbueZwBBgjANsMVAYY3LW/8xtGNjVYQ9Et8xS+8ZfQet9Chg4ESp2s1g8hDrLGdAr7p5Vfk3umioNLKha7+TUHqMCgzk4eEElF/sMUpyq/jxdJqvSa+6bkR8vRBQGu0OK5xyxWEk/X/TZBjll+aHuViw4Rxkifk6oktOxjvGzJ1USR00A+wLEbn3sVjHEIdiUntXyh44f93QF1QUh+fJmzcUGyVexyectGdQZcbUVORoeaxI7J/m7LK4ZF2QqINoPi4GfRYLOwHAJlvo7fNyPsh/JdIoJdchDKlCRnIVTsC7lKjc3ljZQlvREZVapeDlN55iB9i0IwRW8I88Cpl69j86VOZNOdgOfryivkJV6Ux/9nTdYb+h2MI4B1qKggHnZH+A85La79vOgSvkMdultSYtLzMk47SW+Vu9M8tY+PBmBpa57JQxxDw1hgKWgIEuNymkulvlTb9M/aSQ/wvgdFQye4pgneSjb3zx9DrfghzggKp5+5IkrE+cnq552xblQJy8BHbWH6WsHvpEDwFuuM7W6//2D4+hj7d81m/2M1z9BYiIb4Au6FVBmJuw6DRxCHhqGhiJFMBEcHTK58RlE6ZUC8llnahbGVRDc/YpNyLU8l1o10R7is2xpxnNYd0rpnElJzOzo49ux7RhwMRviOVl7ViUVjARK1XddAB0ibkziZ0uEijR2MSXAZwN4YIj2YDE98Yz3izF5+/GTbDDMn22lscoGbQQs/pEsQ8S91bY2pOqZmEoeEkIxwHRpNOY65tmvnC8jhr11jGnj3HOjTk+jcrEBcjdQusAbWK/NXPuIFLHm6duthmQnfBYp7QLm38uiOB3TioNfLt6Q+rbTyCJ8GC7R7gvC5fwccjBeL/o6j+NggT56Am7FAK5pVivCS9xXrnVve5EjksmErFuEjD91GTrbqrLxBUOQ3rml3TTjSeTqPKxqWF7sRVTkIRCCePFl6OkBXbvN3OrQU+1iwGfbvdhIFKDbbBypMd5U+Nk6eTChuhtWGDmtvqfN5DrSSnMro0PNWC6scEpD6IwS3pdg7Tr5FGcDyVJQWJxmdHK0LgjNPYXAExvEq5TZyj0qi5bjUq56AAfmBCzkgJ//wHEhd48l+7/22QpmpXAXqPLfmIp+b4EDVQG9s+FNqcQt5azdWRwSB1SPt+LcuhcOPk/9TvUQ7jCwnzvhRRrrJh4xznhcPMxaALVP44YfSALyDtuHt3eo2TVC3MYbZ7n5MNHiN6yn82/9JtQz3I8FdO1OtHoqHwjEAEXfHA/43wxuSFY85vFD60+AH9wNNLymzqkpsiKGYO5PKbmUMsiehoYAtCPdghnHvm9vpID2LA60jcq6mSW7BE/eNLrz0fHnk5fZHlYTR+UXdXIO38eWkEMLp8XHdPNltn8nwBSpSmqgW \ No newline at end of file diff --git a/Tuist/Signing/iBox.Debug.mobileprovision b/Tuist/Signing/iBox.Debug.mobileprovision deleted file mode 100644 index e40fd7b..0000000 Binary files a/Tuist/Signing/iBox.Debug.mobileprovision and /dev/null differ diff --git a/Tuist/Signing/iBox.Release.mobileprovision b/Tuist/Signing/iBox.Release.mobileprovision deleted file mode 100644 index 3c12147..0000000 Binary files a/Tuist/Signing/iBox.Release.mobileprovision and /dev/null differ diff --git a/Tuist/Signing/iBoxShareExtension.Debug.mobileprovision b/Tuist/Signing/iBoxShareExtension.Debug.mobileprovision deleted file mode 100644 index 5e6c2c4..0000000 Binary files a/Tuist/Signing/iBoxShareExtension.Debug.mobileprovision and /dev/null differ diff --git a/Tuist/Signing/iBoxShareExtension.Release.mobileprovision b/Tuist/Signing/iBoxShareExtension.Release.mobileprovision deleted file mode 100644 index b9d8815..0000000 Binary files a/Tuist/Signing/iBoxShareExtension.Release.mobileprovision and /dev/null differ diff --git a/Tuist/Signing/release.cer.encrypted b/Tuist/Signing/release.cer.encrypted deleted file mode 100644 index e0fbb52..0000000 --- a/Tuist/Signing/release.cer.encrypted +++ /dev/null @@ -1 +0,0 @@ -/iI32Po39nIz5A4SRjThfg==-oKHwtQwnz0SLBJKPqimkZiYB6LocREJzbdXO3b9ibQhPPj0DvP1WWjG+DPDqVvX6AN43+ab6IoyNcHZ4uGIzzFYaOemQ2qmiaHjgKXpkGNNjoxQVN5Ka1wJ6NiAqUICUPIuLjiz1GUWOyMAPUnSSP2dj6wG8PpfsjalWFoTwyBUQhXgSryqL72zGGl2qtJgV42PVF9VNkGPhGP3zSdaSXbmWKJI16ijL4UH4xqDs3yPo1aULNoXySS+kYLBSqSFUUUDkqPeYh13ajCtVJJ9kc+SneR4pE0K54jHFBC/Hgl+77nmdfzCiFqMqvZI9bki7AMdFvqBXjG1ENel5V/V3My9i3+7oxURYKrLjAGuIo+99ba9Ot8xwgHAI0b+xfnQ1ztdYBZzmgNuWGT2PCK4RQl+5Sw0VUtEvauvQa9yBecvBEIHb5xwHtzyto4LEqhydeV7z9UPP1grflDqE6t4KG4K4QDbvGOOFITjXT+KzE++OrA1f66nC/0LgYz+Jvsnu9sy8smQjfOKzOrTNw7dsqZ2SOr0rGLPVYjbAqiVFelkv5EcS+XT9PH5BRzc7IkbR0An1hC+ydRqyVkmDQZ3MKL2r0wFABZ3Gmnxg/Rt/3v+QeUdVDl7a/her2SXKsR1McXdNdJ3PffvjdgtOltWnoCHDsx1YTrGTBz5trRJb8T50I5KtFfZNDZ2H7nuqFwzCs/xfmR8t7EK+XqL/SOT5vNYQaZCZMiNJPOijku2qG6YZn1Fvrq3pYNtYqaodgxD4ce5C0Y2m8OcWlh6ExSZYPBL+G1ubK70qhzdsg1X3KXU9bztGXDucUfPFwuJ5LwrBGlmeVdc62FNK3JPeKa0kWRGBEL3z9RX16S6blX5UI1g29iR96w8xlS4iPLWyWAg6SyWiZePaL7m0XhYzletj6k+h07/ZMYwrLtU+FQAJXjdfx6zAoz244eckkTzaMnr/IANtj8VvvP1NaPRnQZlSniWbx4a4vgssWdKw6EK8A00xc9uIFSjfmrqoI/MwwdsNeryrfEztqIHAZ+IQei5UiIuiatrGIHLvJe15PfbwOebVHMgK8Q1zpSsDrWZOIGMV3QipXzPSBzj7xBJ/Jyn1Ch13omwTm4hLD3g11BFIanVXeSAJc2GPBY1dV+yayrKRA5xBe83s/iLgE8wSCYxuRwzCA8kNLOzXdz19xpUqiL4jVL2sp1suiygpgcI/gv5s9HK6Mx+n6Ox7n8W9WXjwS+s0UueDJcLuAaXV2awUcm2ER7yXxU6/OVKO/2IUMr/wUV5gu46ua8r+RUYtDcS6eUak6NunabQTn/14Cp4MXC0BdZoNzHBtwffdE1XET2wEE5dnFrxv3bF097TaWMDElYOVBks0aSK2E26rb0I4BELA0qrA7vUloDhX0cSgRP0r/S+/CWFHh/0wNGIs3Bjb2PhFB3Z//wHXNac69E9VktgEDd4f0pZWaFFW/v+MdMTYezHbKciRebnmVvzJmoHCtoWQZwVdIOegtwipq+qAwKPbvBFnomesh210KnouW6ttKZNhUchEywRpISRmW6l5zmh/ediymvzdCRJFBg8a5nINNXNbVA6Q8TCjLbaimdtgJQ4ydpHqf1BGDC6SF54lMAoV5VzbLVTd/XvEAPwOzk+Vo4IAKWJUDLL8hUIZlNzDF6PK4xa9BbA3uNaV/QjctfJn6FnaHeQrbKv4NWYFGOTs8S5OnCbRcvWkmeCOehOkStytZE0vCu6M0CeNOh7dNSXJjWtYex7j+eCPCdH4q9aUH4IETpZu1fhVkdlmsJsMwZSSU9gWZlE53aLNgebPQe2C2IQwCL3s1ww75qtSIwfuQCQrjgfTTtogm1g9qxyNi2xDeqgKK9kfHLvTaKgVq7KWrTKsSElEbMINsveicHhTVsC2m+YpeSYS/XsNiJxVgNTb1ADhb6Mb+QGS05/nGR9gaLc4o9LtJc0Pq4SFnHZM5G3JG/GvNV8= \ No newline at end of file diff --git a/Tuist/Signing/release.p12.encrypted b/Tuist/Signing/release.p12.encrypted deleted file mode 100644 index d875ef4..0000000 --- a/Tuist/Signing/release.p12.encrypted +++ /dev/null @@ -1 +0,0 @@ -mAAi3BCoeYUJm5Kl56ye4g==-9rSwPh46VlUcG3oarUGvTzrwCuDxv6qidrecyIJgIc1wtnm4zG5DD3QHgGfnxQpqnxniVt8TS8VNMk0mzG6I4rmWWwG3XPolvFjeb3FWSpWBWsyvkhO+S0vGMLNp07UYK1n1WJ0vaeDoVVKekvt924q1W/kfjRFaofUaQi1dj2zOyUproDNi6bUDeXkbsooh8y6xFh4FJ/wFDdUF0wT1dm71BbROFmGlJKiKiH/foxVGxTlj1Fvx0Atc/+OTpG37MF94irLa0+MmIkAsyCViTWe0dctLc04yfpMNTT93TI/vTtqiawRLChTruDTveOiEjM3mVDvbgVR5SPDEHk3JctGBbu4KN2vCLcESqQorBHaPl9y7vwO7WWFVKP6qmo0pKv//F0sQoDKj17x1DP1EyvGpFmyVqFpGJBsee/CaH0bugnidc2dxDckHOyqv1YlOxJyhiAbS3QZUVopy//g+E6CsKJg21ONC1UA1Sjp2yQ5J9bQ7jLvzkqguj6IFAUiAJ46wu+kF1BNV6reOETjE/gImiKUAfp4HMwXPiepCcoP3WftSm2KowVWje4oW+dgK5ppcT9Rxa7w5yA4uSV4smIdKJT51Wo1Viex3tAXWkhf2lth3ASYXYc/AhoHR3jXuZyiqs00vaWJpVmQsqwsV0Kp6CD4/sy3td0JJhmtXqH11Dleye1PEi4JrnfYSiVKm78VTnoXx26Gm9y4U7Y2uwsWT8RLAa57r/VAekuEuA+j/s7qqTpTyLAvGG9Xee68pgGkbBlqf1KUZdD9PkF0OI0C11Tdwwo3by/wy3vaW56IyLB+MvXegRkVVtdzjGq8srtq2seESDk1ZVncbZUDZBHV116aC/UvFZz6PTiWONM8LkVq+VqLgYz1NEuX2fJTy1e0lG55OhCIjYsAzavqLdC+x9xH83QK02YqFAf2xpIeguSO07Edcp85tsMSQKNd3oBnn04313UryF54nkYM5K4mXZKkrg4+dBaVtyu1HfPkYXB/thXD/dm4+uiHoSrFAV/6L/TOCQrpXoGJm8wfyNIv9Galh0LMjFKVhhKTeF4/FZ5+fD6e2BzfDrZJbPAJpiezwHlvx1WfSKB6cY5LI3we3q1qlRfZ74fA/8Xp31q6F6LADbW8UYcRMKaO+bPsVetxggvs1GEjiemGe3m/I99Z3qMpYtP1zl0R2pA3a5puoKwLF0f+sSZGjsx++e0EGEzB4AwvF4ZdBGE5OAc7E++obrwuGCKjSCY9wjHAxyXY+kxaN/E9hFprDWp/FiMbh01ECpy051JdX5JS0ay0/gPaJDtR/upH/1dzERzYbH9bCvcxXAAOAV2FRWX01JMVqLeaN+B4+mhuy+4MbcEhbr4H9sHcWLrUXOJMFOYJ7mZqCX7cuY1zQhiGKQc1XPhoXnfxYoeX/2CZrT2dNcL2OIR6eCpd0Eqsy81cIzDcvHkqYTYm9QE+LeAIJzab9EkLIk/gB1s8GjY80SZPofWnFNdY2M0UPbttVzZmkzTq7lOib2O8gCb6Hx6SyZ6APfmDdWcJzc760jzn4BV3zy/0HDxQyxX9+Y95AsELXMF36grk/2LM42hP0rmsxVO7Zanf2orcM3cLxzX8yfNLO/y3Wy/d5v6EecAaV/EM/tbq7uCHgmHOXaXsCog2AiuEFSE/twlLQH+472UACpTs7dOMt1CxSbf43RSAeg6e4M4Y/fM8x/ufRzZ+Abj4i4fvqsnHdO2LXSjW3BWJTYXXihjDH5VOXWblw+KbPBhpHf3Vq9+0VtU8L6IoX/mvI+jif5NPrs8KibP3rUM0dbfY005xdV7VHWbPD3cN6OxI68c+JZjMMTGWosOrwNp9a5ERJ3WlkrIQtY4XCvCUjZ3xVThiO5JdwB8tijwRsiUkdmWJwJOPHz75ON4rQkPrcRl1zlrmgXGDF2EAw98gN5mKXVNMUmuxhHH9aRT/GPVvqPgzPG6h2IC9WQaWvb4ta7YYRu0k3sQuZhSAfxRTsCMg0rUo272xaZhrGVTMgdt9yj8gBsvrkVh87XOv7KDgksusC4QJjYDDgYR5KwkaxD2CVu90PJDqIFOg9+BkzksDH7Ls0zq8tVLQfBvbENnXy7VYwSvobxLtSimscXQbH6TcaJVaTI6W7UzzSOdV6DiDYan3NncFE/DacUP8Z1GNpsbPKUOlyCfEPBQWBKLuY+IdArT+kxbjwR74NMEgPVUcIO+50OHId3VMwIVzQ2DjY29t4HrXMpvN2HQK3tstvKstkIv/piHg0MGCSr/i9Enj6V/KSoJ/NzGulPlVXJG8n1R4pDgF3pQmJE09P87p418oLehCCanuojoRwQSkWGpNBHvjlceUNmXCQbxW0aCPKxn3TLj9aJLYYUIoWxNT6m+viNbCdaIeGwm6S/PLTrfePESuPMGc/EDmsTXkuQ65edhtxlKFlqTdhyGpkrNfK70fvo7DUhYZimWvIR2cjC5CA28PqqpCRFj6R4bQ1+dJTId0kbvDh1J7LYTIfXeOblt7J8v0lv0izUmHGMDLtZtf8IBCci9Dd+b4i68Mhtoi7suO/lHm6JeaB1UkyhhbUja7jh8q1tpkms7O6D/HfMhr63NLwjL6hKu6GW+pFO7mQr3pPIR94jHaAYIssddfEQgZmu7jxYeuf9eC9hkk9wJIzBZ+rDAjed2qevRygaA2NwSIRtoOLKnjRUd2fb8JttTYZ4gNMr+tOtWc0EekKP6m5SJaBZ31gNXSFMYrq9Xp5/3uj4MD/pY0600M330SitU7G4qZTzCmV8w8AuAX2a4YRXyPKLq+IVppwBbGhf20fjnaYB4njnMlOgHq5ImkBAt6IuVUZ5CCeHxOxkHqbzZQCWSZRCXR4G9MVszrv/gWio13XVcqQxNeI0zitWAoq5/MZXgRXEbiCVDQZ+Aa4EqjKNP3IOq7hraK1jPxpDo2JTprAqS6Lg7BURfFWixrdcbmYG6i3ZjpmXd12+Ob3zfEh07fVOo1AZCmQsVLBkC+mSgSMIk83fBRW2YrAhBjA8t1BXkKb9cJPZ7vZnPmohERagm3YVFOaauPXKc/OB2wkHLiqYgcLA4o6UK43WCK3ds09nHvqn2TgtjUh/hv9Th1HRvHWB78nJmPyuyq2czC1uvqeOwHiGMvTVaFOOMR8Tb27vyeULG/sVdoKIMKltUT0/H2kx2w7syjw4CvYeda77OBvqgAipAvkP+zOjmtnIi8chBqH7528gs/73Oa/Er3nT9UWfYLyMMrgNZsmU1S6xiYFM3gnb4u4PMDiOjUrAJsXfbh9jalBshdeh8R7+7j5et7xt+xV5pUh+r0rubQ5e0Cz+JyDdRR7VsJMQjrZ+vmPXYUQz3gYO8NO4I3nUl2W8zv3jDGcjU53edoWCu2zLdHCi0buhGdoYsl5Oys4aWZcqzY3tk1QLJDVKGju8APFmTjK7d7rVVSJC9hLaNegmD/60h0m7Sl6ZW4NvmntCl78XkXNss0k1aw8zQLC0MwTgxTOYEhghk+53BNTla4zTfA9ejQfxHLR8LBxHG/U3+yIp30D9zHry9c8Lcp5tN6oLGknIgEP5/ojIQhvMnUnJSDg3pYG2ewqfbqLmQrf7EVFdsXMqNCHFnheftZgzJP+soddfV31vYHGHgmgx9siHZ9GtGgtaBwGHmOiVlC2za2faVR1ZCF47hhBD5yLSmH7MyXcZQ84/LHgeUNiUmtU/22nGnyubVaEw68RR7cIH9nr2XonuAAwhhfrdlgYQLcSHm3/AGauKwPq+3eJCQQ9qUmOqzOnObsccCirAOgJsVAAtVrYZAAFd8W/KwGYRYIqhiLMzNEWIFe3NldRHPcjDV+PUI2Mw+he6ovCaSgp9CRwFNGK/PixGekyYhMK5M6yqAEW9IPxz1Fv4PIIL43sGJLm3UKcaAVxk8RQC4U7vBqE4/++J20VjQBBpd40AWO1VSmMTi4TwURvp1rsaqMaPCJvF+QKtH3nAe+ZLOZylE5M5D068u8kOXvvVv2Uj9FdlJdgrT053c+cv6u5foHlHVayLIuYROkLiNHNzRm2088VtvhKvviVL5LiiHqSL4RaLT/lTvwPOHLry8o0DYldHM1nmQRgCG9ZVXQNmbJgcaA8fXJ5g0jk1viwJo2tkgNTinjvz0iusFq51MrgdSsj/hZptzdSm1qtozdvDCikLiuEn3jEO1M8WXnxZGKYVOYksIbMlHIxdLSKgF5Py1N81pPDyZyzMXv6dfczuhiEjOWyPQ0jKBNm4YJ0Xbq8p4SD8SqjyrhJLWIL+hCV8okwkfJg+2GaJ0XtsIQYZE3248PdgRwnn9rS/iGmtJpyW9NoeAeR2AFiLI9B22L4l7zws0DVocvdV3sh \ No newline at end of file diff --git a/fastlane/Appfile b/fastlane/Appfile deleted file mode 100644 index ead9487..0000000 --- a/fastlane/Appfile +++ /dev/null @@ -1,8 +0,0 @@ -# app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app -# apple_id("[[APPLE_ID]]") # Your Apple Developer Portal username - - -# For more information about the Appfile, see: -# https://docs.fastlane.tools/advanced/#appfile - -app_identifier("com.box42.iBox") diff --git a/fastlane/Fastfile b/fastlane/Fastfile deleted file mode 100644 index 1c196f1..0000000 --- a/fastlane/Fastfile +++ /dev/null @@ -1,73 +0,0 @@ -# This file contains the fastlane.tools configuration -# You can find the documentation at https://docs.fastlane.tools -# -# For a list of all available actions, check out -# -# https://docs.fastlane.tools/actions -# -# For a list of all available plugins, check out -# -# https://docs.fastlane.tools/plugins/available-plugins -# - -# Uncomment the line if you want fastlane to automatically update itself -# update_fastlane - -# Constants -APP_NAME = "iBox" -SCHEME = "iBox" -BUNDLE_ID = "com.box42.iBox" - -KEYCHAIN_NAME = ENV["KEYCHAIN_NAME"] -KEYCHAIN_PASSWORD = ENV["KEYCHAIN_PASSWORD"] - -default_platform(:ios) - -platform :ios do - # Keychain - desc "Save To Keychain" - lane :set_keychain do |options| - create_keychain( - name: "#{KEYCHAIN_NAME}", - password: "#{KEYCHAIN_PASSWORD}", - default_keychain: true, - unlock: true, - timeout: 3600, - lock_when_sleeps: true - ) - - import_certificate( - certificate_path: "Tuist/Signing/release.cer", - keychain_name: "#{KEYCHAIN_NAME}", - keychain_password: "#{KEYCHAIN_PASSWORD}" - ) - - import_certificate( - certificate_path: "Tuist/Signing/release.p12", - keychain_name: "#{KEYCHAIN_NAME}", - keychain_password: "#{KEYCHAIN_PASSWORD}" - ) - - install_provisioning_profile(path: "Tuist/Signing/#{APP_NAME}.Release.mobileprovision") - end - - # Upload TestFlight - desc "Push to TestFlight" - lane :tf do |options| - # AppStore Connect API key - app_store_connect_api_key(is_key_content_base64: true, in_house: false) - - # BuildNumber Up - increment_build_number({ build_number: latest_testflight_build_number() + 1 }) - - # Build App - build_app( - workspace: "#{APP_NAME}.xcworkspace", - scheme: "#{SCHEME}", - export_method: "app-store" - ) - - # Upload to TestFlight - upload_to_testflight(skip_waiting_for_build_processing: true) - end -end diff --git a/fastlane/README.md b/fastlane/README.md deleted file mode 100644 index 5bde376..0000000 --- a/fastlane/README.md +++ /dev/null @@ -1,40 +0,0 @@ -fastlane documentation ----- - -# Installation - -Make sure you have the latest version of the Xcode command line tools installed: - -```sh -xcode-select --install -``` - -For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) - -# Available Actions - -## iOS - -### ios set_keychain - -```sh -[bundle exec] fastlane ios set_keychain -``` - -Save To Keychain - -### ios tf - -```sh -[bundle exec] fastlane ios tf -``` - -Push to TestFlight - ----- - -This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. - -More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). - -The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/fastlane/report.xml b/fastlane/report.xml deleted file mode 100644 index 7abd9a5..0000000 --- a/fastlane/report.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/fox/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/fox/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/fox/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page0.imageset/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page0.imageset/Contents.json deleted file mode 100644 index de4782f..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page0.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "fox0.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page0.imageset/fox0.png b/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page0.imageset/fox0.png deleted file mode 100644 index 0892874..0000000 Binary files a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page0.imageset/fox0.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page1.imageset/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page1.imageset/Contents.json deleted file mode 100644 index 3efa561..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page1.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "fox1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page1.imageset/fox1.png b/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page1.imageset/fox1.png deleted file mode 100644 index 14ede17..0000000 Binary files a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page1.imageset/fox1.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page2.imageset/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page2.imageset/Contents.json deleted file mode 100644 index 9e46b91..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page2.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "fox2.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page2.imageset/fox2.png b/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page2.imageset/fox2.png deleted file mode 100644 index 2120ecd..0000000 Binary files a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page2.imageset/fox2.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page3.imageset/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page3.imageset/Contents.json deleted file mode 100644 index ea3a111..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page3.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "fox3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page3.imageset/fox3.png b/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page3.imageset/fox3.png deleted file mode 100644 index e15e2be..0000000 Binary files a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page3.imageset/fox3.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page4.imageset/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page4.imageset/Contents.json deleted file mode 100644 index a772154..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page4.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "fox4.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page4.imageset/fox4.png b/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page4.imageset/fox4.png deleted file mode 100644 index 352f185..0000000 Binary files a/iBox/Resources/Assets.xcassets/42pack_icon/fox/fox_page4.imageset/fox4.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox0.imageset/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox0.imageset/Contents.json deleted file mode 100644 index 9190e47..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox0.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "sitting_fox0.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox0.imageset/sitting_fox0.png b/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox0.imageset/sitting_fox0.png deleted file mode 100644 index 61410e0..0000000 Binary files a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox0.imageset/sitting_fox0.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox1.imageset/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox1.imageset/Contents.json deleted file mode 100644 index 688a821..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox1.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "sitting_fox1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox1.imageset/sitting_fox1.png b/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox1.imageset/sitting_fox1.png deleted file mode 100644 index 4ea17f8..0000000 Binary files a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox1.imageset/sitting_fox1.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox2.imageset/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox2.imageset/Contents.json deleted file mode 100644 index 9321c41..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox2.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "sitting_fox2.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox2.imageset/sitting_fox2.png b/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox2.imageset/sitting_fox2.png deleted file mode 100644 index 050be9b..0000000 Binary files a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox2.imageset/sitting_fox2.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox3.imageset/Contents.json b/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox3.imageset/Contents.json deleted file mode 100644 index 5acd3eb..0000000 --- a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox3.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "sitting_fox3.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox3.imageset/sitting_fox3.png b/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox3.imageset/sitting_fox3.png deleted file mode 100644 index 4ea17f8..0000000 Binary files a/iBox/Resources/Assets.xcassets/42pack_icon/sitting_fox/sitting_fox3.imageset/sitting_fox3.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/100.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/100.png deleted file mode 100644 index 7b88f33..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/100.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/1024.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/1024.png deleted file mode 100644 index a738dda..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/1024.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/114.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/114.png deleted file mode 100644 index 3051489..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/114.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/120.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/120.png deleted file mode 100644 index 6534722..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/120.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/144.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/144.png deleted file mode 100644 index 209db66..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/144.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/152.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/152.png deleted file mode 100644 index 1d499a1..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/152.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/167.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/167.png deleted file mode 100644 index c937d4e..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/167.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/180.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/180.png deleted file mode 100644 index 5c3c88b..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/180.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/20.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/20.png deleted file mode 100644 index 24d309e..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/20.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/29.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/29.png deleted file mode 100644 index a1866dd..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/29.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/40.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/40.png deleted file mode 100644 index ff1d8bd..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/40.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/50.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/50.png deleted file mode 100644 index edd2f93..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/50.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/57.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/57.png deleted file mode 100644 index 3056ce6..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/57.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/58.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/58.png deleted file mode 100644 index 6ea7e65..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/58.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/60.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/60.png deleted file mode 100644 index be3cc60..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/60.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/72.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/72.png deleted file mode 100644 index aac3de7..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/72.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/76.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/76.png deleted file mode 100644 index 4ae12b2..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/76.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/80.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/80.png deleted file mode 100644 index 3e16efc..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/80.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/87.png b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/87.png deleted file mode 100644 index 1bee41f..0000000 Binary files a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/87.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index 4fdf882..13613e3 100644 --- a/iBox/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/iBox/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,153 +1,8 @@ { "images" : [ { - "filename" : "40.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "60.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "29.png", - "idiom" : "iphone", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "58.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "87.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "80.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "120.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "57.png", - "idiom" : "iphone", - "scale" : "1x", - "size" : "57x57" - }, - { - "filename" : "114.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "57x57" - }, - { - "filename" : "120.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "180.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "20.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "40.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "29.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "58.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "40.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "80.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "50.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "50x50" - }, - { - "filename" : "100.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "50x50" - }, - { - "filename" : "72.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "72x72" - }, - { - "filename" : "144.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "72x72" - }, - { - "filename" : "76.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "152.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "167.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "1024.png", - "idiom" : "ios-marketing", - "scale" : "1x", + "idiom" : "universal", + "platform" : "ios", "size" : "1024x1024" } ], diff --git a/iBox/Resources/Assets.xcassets/LaunchIcon.imageset/Contents.json b/iBox/Resources/Assets.xcassets/LaunchIcon.imageset/Contents.json deleted file mode 100644 index ed1ff73..0000000 --- a/iBox/Resources/Assets.xcassets/LaunchIcon.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "LaunchIcon.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iBox/Resources/Assets.xcassets/LaunchIcon.imageset/LaunchIcon.png b/iBox/Resources/Assets.xcassets/LaunchIcon.imageset/LaunchIcon.png deleted file mode 100644 index d17824a..0000000 Binary files a/iBox/Resources/Assets.xcassets/LaunchIcon.imageset/LaunchIcon.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/Logo/1024.png b/iBox/Resources/Assets.xcassets/Logo/1024.png deleted file mode 100644 index 83ffffa..0000000 Binary files a/iBox/Resources/Assets.xcassets/Logo/1024.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/Logo/128.imageset/128 1.png b/iBox/Resources/Assets.xcassets/Logo/128.imageset/128 1.png deleted file mode 100644 index 077d76c..0000000 Binary files a/iBox/Resources/Assets.xcassets/Logo/128.imageset/128 1.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/Logo/128.imageset/128.png b/iBox/Resources/Assets.xcassets/Logo/128.imageset/128.png deleted file mode 100644 index 077d76c..0000000 Binary files a/iBox/Resources/Assets.xcassets/Logo/128.imageset/128.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/Logo/128.imageset/Contents.json b/iBox/Resources/Assets.xcassets/Logo/128.imageset/Contents.json deleted file mode 100644 index 8b99860..0000000 --- a/iBox/Resources/Assets.xcassets/Logo/128.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "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/iBox/Resources/Assets.xcassets/Logo/128.png b/iBox/Resources/Assets.xcassets/Logo/128.png deleted file mode 100644 index 077d76c..0000000 Binary files a/iBox/Resources/Assets.xcassets/Logo/128.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/Logo/256.png b/iBox/Resources/Assets.xcassets/Logo/256.png deleted file mode 100644 index fd64f79..0000000 Binary files a/iBox/Resources/Assets.xcassets/Logo/256.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/Logo/32.png b/iBox/Resources/Assets.xcassets/Logo/32.png deleted file mode 100644 index 7a15387..0000000 Binary files a/iBox/Resources/Assets.xcassets/Logo/32.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/Logo/512.png b/iBox/Resources/Assets.xcassets/Logo/512.png deleted file mode 100644 index 2d39060..0000000 Binary files a/iBox/Resources/Assets.xcassets/Logo/512.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/Logo/64.png b/iBox/Resources/Assets.xcassets/Logo/64.png deleted file mode 100644 index ad70cb5..0000000 Binary files a/iBox/Resources/Assets.xcassets/Logo/64.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/Logo/Contents.json b/iBox/Resources/Assets.xcassets/Logo/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/iBox/Resources/Assets.xcassets/Logo/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iBox/Resources/Assets.xcassets/appstore.imageset/Contents.json b/iBox/Resources/Assets.xcassets/appstore.imageset/Contents.json deleted file mode 100644 index a0b3a22..0000000 --- a/iBox/Resources/Assets.xcassets/appstore.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "appstore.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iBox/Resources/Assets.xcassets/appstore.imageset/appstore.png b/iBox/Resources/Assets.xcassets/appstore.imageset/appstore.png deleted file mode 100644 index a738dda..0000000 Binary files a/iBox/Resources/Assets.xcassets/appstore.imageset/appstore.png and /dev/null differ diff --git a/iBox/Resources/Assets.xcassets/playstore.imageset/Contents.json b/iBox/Resources/Assets.xcassets/playstore.imageset/Contents.json deleted file mode 100644 index 861f456..0000000 --- a/iBox/Resources/Assets.xcassets/playstore.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "playstore.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iBox/Resources/Assets.xcassets/playstore.imageset/playstore.png b/iBox/Resources/Assets.xcassets/playstore.imageset/playstore.png deleted file mode 100644 index 77b8432..0000000 Binary files a/iBox/Resources/Assets.xcassets/playstore.imageset/playstore.png and /dev/null differ diff --git a/iBox/Resources/Version b/iBox/Resources/Version deleted file mode 160000 index 57b2e79..0000000 --- a/iBox/Resources/Version +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 57b2e79219b13e129aae1521374580fff722defd diff --git a/iBox/Resources/iBox.xcdatamodeld/iBox.xcdatamodel/contents b/iBox/Resources/iBox.xcdatamodeld/iBox.xcdatamodel/contents index 16ddc4f..50d2514 100644 --- a/iBox/Resources/iBox.xcdatamodeld/iBox.xcdatamodel/contents +++ b/iBox/Resources/iBox.xcdatamodeld/iBox.xcdatamodel/contents @@ -1,16 +1,4 @@ - - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/iBox/Sources/AddBookmark/AddBookmarkView.swift b/iBox/Sources/AddBookmark/AddBookmarkView.swift deleted file mode 100644 index e8d7d99..0000000 --- a/iBox/Sources/AddBookmark/AddBookmarkView.swift +++ /dev/null @@ -1,312 +0,0 @@ -// -// AddBookmarkView.swift -// iBox -// -// Created by jiyeon on 1/5/24. -// - -import UIKit -import Combine - -import SkeletonView -import SnapKit - -class AddBookmarkView: UIView { - - var cancellables = Set() - - var onButtonTapped: (() -> Void)? - var onTextChange: ((Bool) -> Void)? - - var selectedFolderName: String? { - didSet { - selectedFolderLabel.text = selectedFolderName - } - } - - // MARK: - UI Components - - private let textFieldView: UIView = UIView().then { - $0.backgroundColor = UIColor.backgroundColor - $0.layer.cornerRadius = 20 - $0.clipsToBounds = true - } - - private let nameTextViewPlaceHolder = UILabel().then { - $0.text = "뢁마크 이름" - $0.font = .cellTitleFont - $0.textColor = .systemGray3 - $0.isSkeletonable = true - $0.isHiddenWhenSkeletonIsActive = true - } - - let nameTextView = UITextView().then { - $0.backgroundColor = .clear - $0.layer.borderWidth = 0 - $0.textContainerInset = UIEdgeInsets(top: 7, left: 0, bottom: 0, right: 0) - $0.font = .cellTitleFont - $0.textColor = .label - $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 { - $0.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) - $0.tintColor = .systemGray3 - $0.isHidden = true - } - - private let separatorView = UIView().then { - $0.backgroundColor = .systemGray3 - } - - private let urlTextViewPlaceHolder = UILabel().then { - $0.text = "URL" - $0.font = .cellTitleFont - $0.textColor = .systemGray3 - $0.isSkeletonable = true - $0.isHiddenWhenSkeletonIsActive = true - } - - let urlTextView = UITextView().then { - $0.backgroundColor = .clear - $0.layer.borderWidth = 0 - $0.textContainerInset = UIEdgeInsets(top: 7, left: 0, bottom: 0, right: 0) - $0.font = .cellTitleFont - $0.textColor = .label - $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 { - $0.backgroundColor = UIColor.backgroundColor - $0.layer.cornerRadius = 10 - $0.clipsToBounds = true - $0.titleLabel?.font = .cellTitleFont - $0.isEnabled = true - } - - private let buttonLabel = UILabel().then { - $0.text = "λͺ©λ‘" - $0.font = .cellTitleFont - $0.textColor = .label - } - - let selectedFolderLabel = UILabel().then { - $0.font = .descriptionFont - $0.textColor = .systemGray - $0.textAlignment = .right - } - - private let chevronImageView = UIImageView().then { - let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .medium, scale: .default) - let image = UIImage(systemName: "chevron.forward", withConfiguration: config)?.withRenderingMode(.alwaysTemplate) - $0.image = image - $0.tintColor = .systemGray3 - $0.contentMode = .scaleAspectFit - } - - // MARK: - Initializer - - override init(frame: CGRect) { - super.init(frame: frame) - setupProperty() - setupHierarchy() - setupLayout() - setupBindings() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - } - - deinit { - AddBookmarkManager.shared.incomingTitle = nil - AddBookmarkManager.shared.incomingData = nil - AddBookmarkManager.shared.incomingFaviconUrl = nil - AddBookmarkManager.shared.incomingError = nil - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .systemGroupedBackground - clearButton.addTarget(self, action: #selector(clearTextView), for: .touchUpInside) - button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) - nameTextView.delegate = self - urlTextView.delegate = self - - isSkeletonable = true - } - - private func setupHierarchy() { - addSubview(textFieldView) - addSubview(nameTextView) - addSubview(clearButton) - addSubview(separatorView) - addSubview(urlTextView) - addSubview(nameTextViewPlaceHolder) - addSubview(urlTextViewPlaceHolder) - addSubview(button) - addSubview(buttonLabel) - addSubview(selectedFolderLabel) - addSubview(chevronImageView) - } - - private func setupLayout() { - - textFieldView.snp.makeConstraints { make in - make.top.equalToSuperview().offset(70) - make.height.equalTo(150) - make.leading.equalToSuperview().offset(20) - make.trailing.equalToSuperview().offset(-20) - } - - nameTextView.snp.makeConstraints { make in - make.top.equalTo(textFieldView.snp.top).offset(10) - make.leading.equalTo(textFieldView.snp.leading).offset(15) - make.trailing.equalTo(clearButton.snp.leading) - make.height.equalTo(30) - } - - nameTextViewPlaceHolder.snp.makeConstraints { make in - make.top.equalTo(nameTextView.snp.top).offset(7) - make.leading.equalTo(nameTextView.snp.leading).offset(5) - } - - clearButton.snp.makeConstraints { make in - make.top.equalTo(nameTextView.snp.top).offset(7) - make.trailing.equalTo(textFieldView.snp.trailing).offset(-15) - make.width.height.equalTo(24) - } - - separatorView.snp.makeConstraints { make in - make.top.equalTo(nameTextView.snp.bottom).offset(10) - make.leading.equalTo(nameTextView.snp.leading).offset(5) - make.trailing.equalTo(textFieldView.snp.trailing).offset(-15) - make.height.equalTo(1) - } - - urlTextView.snp.makeConstraints { make in - make.top.equalTo(separatorView.snp.bottom).offset(10) - make.leading.trailing.equalTo(nameTextView) - make.bottom.equalTo(textFieldView.snp.bottom).offset(-10) - } - - urlTextViewPlaceHolder.snp.makeConstraints { make in - make.top.equalTo(urlTextView.snp.top).offset(7) - make.leading.equalTo(urlTextView.snp.leading).offset(5) - } - - button.snp.makeConstraints { make in - make.top.equalTo(textFieldView.snp.bottom).offset(20) - make.leading.equalToSuperview().offset(20) - make.trailing.equalToSuperview().offset(-20) - make.height.equalTo(50) - } - - buttonLabel.snp.makeConstraints { make in - make.leading.equalTo(button.snp.leading).offset(20) - make.centerY.equalTo(button.snp.centerY) - make.height.equalTo(40) - } - - selectedFolderLabel.snp.makeConstraints { make in - make.trailing.equalTo(chevronImageView.snp.leading).offset(-10) - make.centerY.equalTo(button.snp.centerY) - make.height.equalTo(40) - make.width.equalTo(200) - } - - chevronImageView.snp.makeConstraints { make in - make.trailing.equalTo(button.snp.trailing).offset(-20) - make.centerY.equalTo(button.snp.centerY) - make.width.height.equalTo(15) - } - - } - - 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() - } - .store(in: &cancellables) - - 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() { - let isBothTextViewsFilled = !(nameTextView.text?.isEmpty ?? true) && !(urlTextView.text?.isEmpty ?? true) - onTextChange?(isBothTextViewsFilled) - } - - @objc func clearTextView() { - nameTextView.text = "" - textViewDidChange(nameTextView) - nameTextView.becomeFirstResponder() - } - - @objc private func buttonTapped() { - onButtonTapped?() - } - - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - self.endEditing(true) - } -} - -extension AddBookmarkView: UITextViewDelegate { - func textViewDidBeginEditing(_ textView: UITextView) { - let textLength: Int = textView.text.count - textView.selectedRange = NSRange(location: textLength, length: 0) - } - - func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - if text == "\n" { - if textView == nameTextView { - urlTextView.becomeFirstResponder() - } else if textView == urlTextView { - textView.resignFirstResponder() - } - return false - } - return true - } - - func textViewDidChange(_ textView: UITextView) { - - let isBothTextViewsFilled = !nameTextView.text.isEmpty && !urlTextView.text.isEmpty - onTextChange?(isBothTextViewsFilled) - - if textView == nameTextView { - nameTextViewPlaceHolder.isHidden = !nameTextView.text.isEmpty - clearButton.isHidden = nameTextView.text.isEmpty - } - - if textView == urlTextView { - urlTextViewPlaceHolder.isHidden = !urlTextView.text.isEmpty - } - - } -} diff --git a/iBox/Sources/AddBookmark/AddBookmarkViewController.swift b/iBox/Sources/AddBookmark/AddBookmarkViewController.swift deleted file mode 100644 index 0388234..0000000 --- a/iBox/Sources/AddBookmark/AddBookmarkViewController.swift +++ /dev/null @@ -1,226 +0,0 @@ -// -// AddBookmarkViewController.swift -// iBox -// -// 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) -} - -final class AddBookmarkViewController: UIViewController { - weak var delegate: AddBookmarkViewControllerProtocol? - - var cancellables = Set() - - var haveValidInput = false - var selectedFolder: Folder? - var selectedFolderIndex: Int? - var folders = [Folder]() - - let addBookmarkView = AddBookmarkView() - - override func loadView() { - super.loadView() - setupAddBookmarkView() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - updateSelectedFolder() - addBookmarkView.updateTextFieldsFilledState() - } - - override func viewDidLoad() { - super.viewDidLoad() - setupNavigationBar() - updateSelectedFolder() - addBookmarkView.nameTextView.becomeFirstResponder() - setupBindings() - - view.isSkeletonable = true - } - - private func setupNavigationBar() { - let appearance = UINavigationBarAppearance() - appearance.configureWithTransparentBackground() - appearance.titleTextAttributes = [.font: UIFont.subTitlefont] - - navigationController?.navigationBar.tintColor = .box - navigationController?.navigationBar.standardAppearance = appearance - navigationController?.navigationBar.compactAppearance = appearance - navigationController?.navigationBar.scrollEdgeAppearance = appearance - - title = "μƒˆλ‘œμš΄ 뢁마크" - - navigationItem.leftBarButtonItem = UIBarButtonItem(title: "μ·¨μ†Œ", style: .plain, target: self, action: #selector(cancelButtonTapped)) - navigationItem.rightBarButtonItem = UIBarButtonItem(title: "μΆ”κ°€", style: .plain, target: self, action: #selector(addButtonTapped)) - navigationItem.rightBarButtonItem?.isEnabled = false - - let attributes: [NSAttributedString.Key: Any] = [ - .font: UIFont.barItemFont - ] - navigationItem.leftBarButtonItem?.setTitleTextAttributes(attributes, for: .normal) - navigationItem.rightBarButtonItem?.setTitleTextAttributes(attributes, for: .normal) - navigationItem.rightBarButtonItem?.setTitleTextAttributes(attributes, for: .disabled) - } - - private func setupAddBookmarkView() { - addBookmarkView.onButtonTapped = { [weak self] in - self?.openFolderSelection() - } - addBookmarkView.onTextChange = { [weak self] isEnabled in - self?.haveValidInput = isEnabled - - if let haveValidInput = self?.haveValidInput, - haveValidInput, - let _ = self?.selectedFolder { - self?.navigationItem.rightBarButtonItem?.isEnabled = true - } else { - self?.navigationItem.rightBarButtonItem?.isEnabled = false - } - } - view = addBookmarkView - } - - private func updateSelectedFolder() { - folders = CoreDataManager.shared.getFolders() - let selectedFolderId = UserDefaultsManager.selectedFolderId - - for (index, folder) in folders.enumerated() { - if folder.id == selectedFolderId { - selectedFolder = folder - selectedFolderIndex = index - } - } - - if selectedFolder == nil && !folders.isEmpty { - selectedFolder = folders[0] - selectedFolderIndex = 0 - } - - if let selectedFolder { - addBookmarkView.selectedFolderName = selectedFolder.name - } else { - addBookmarkView.selectedFolderName = "μ„ νƒλœ 폴더가 μ—†μŠ΅λ‹ˆλ‹€." - } - } - - 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 - - if isTextFieldsEmpty { - self.dismiss(animated: true, completion: nil) - } else { - let alertController = UIAlertController(title: nil, message: "변경사항 폐기", preferredStyle: .alert) - - - let discardAction = UIAlertAction(title: "예", style: .destructive) { [weak self] _ in - self?.dismiss(animated: true, completion: nil) - } - - let cancelAction = UIAlertAction(title: "μ•„λ‹ˆμ˜€", style: .cancel) - - alertController.addAction(discardAction) - alertController.addAction(cancelAction) - - present(alertController, animated: true, completion: nil) - } - } - - @objc private func addButtonTapped() { - guard let name = addBookmarkView.nameTextView.text, !name.isEmpty, - var urlString = addBookmarkView.urlTextView.text, !urlString.isEmpty else { - print("Invalid input") - return - } - - let lowercasedUrlString = urlString.lowercased() - if !lowercasedUrlString.hasPrefix("https://") && !lowercasedUrlString.hasPrefix("http://") { - urlString = "https://" + urlString - } - - var allowedCharacters = CharacterSet.urlQueryAllowed - allowedCharacters.insert("#") - - guard let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters: allowedCharacters), - let url = URL(string: encodedUrlString) else { - print("Invalid URL format") - return - } - - let newBookmark = Bookmark(id: UUID(), name: name, url: url) - - if let selectedFolder = selectedFolder, - let selectedFolderIndex = selectedFolderIndex { - CoreDataManager.shared.addBookmark(newBookmark, folderId: selectedFolder.id) - delegate?.addBookmarkDirect(newBookmark, at: selectedFolderIndex) - } else { - print("μ„ νƒλœ 폴더가 μ—†μŠ΅λ‹ˆλ‹€.") - } - - self.dismiss(animated: true, completion: nil) - } - - private func openFolderSelection() { - let folderListViewController = FolderListViewController(folders: folders, selectedId: selectedFolder?.id) - folderListViewController.title = "λͺ©λ‘" - folderListViewController.delegate = self - - navigationController?.pushViewController(folderListViewController, animated: true) - } - -} - -extension AddBookmarkViewController: FolderListViewControllerDelegate { - func addFolder(_ folder: Folder) { - delegate?.addFolderDirect(folder) - } - - func selectFolder(_ folder: Folder, at index: Int) { - selectedFolder = folder - selectedFolderIndex = index - - if haveValidInput { - navigationItem.rightBarButtonItem?.isEnabled = true - } - - addBookmarkView.selectedFolderName = selectedFolder?.name - } - -} diff --git a/iBox/Sources/AddBookmark/FolderListCell.swift b/iBox/Sources/AddBookmark/FolderListCell.swift deleted file mode 100644 index 8e86e22..0000000 --- a/iBox/Sources/AddBookmark/FolderListCell.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// FolderListCell.swift -// iBox -// -// Created by μ΅œμ’…μ› on 3/7/24. -// - -import UIKit - -class FolderListCell: UITableViewCell { - - static let reuseIdentifier = "ListCell" - - // MARK: - UI Components - - private let folderImageView = UIImageView().then { - $0.image = UIImage(systemName: "folder.fill") - $0.contentMode = .scaleAspectFit - $0.tintColor = .gray - } - - let folderNameLabel = UILabel().then { - $0.textColor = .label - $0.font = .cellTitleFont - } - - private let checkImageView = UIImageView().then { - let config = UIImage.SymbolConfiguration(pointSize: 14, weight: .semibold, scale: .default) - $0.image = UIImage(systemName: "checkmark", withConfiguration: config) - $0.contentMode = .scaleAspectFit - $0.tintColor = .box - $0.isHidden = true - } - - // MARK: - Initializer - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: .default, reuseIdentifier: reuseIdentifier) - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup Methods - - private func setupProperty() { - self.backgroundColor = .clear - } - - private func setupHierarchy() { - self.contentView.addSubview(folderImageView) - self.contentView.addSubview(folderNameLabel) - self.contentView.addSubview(checkImageView) - } - - func setupLayout() { - folderImageView.snp.makeConstraints { make in - make.centerY.equalToSuperview() - make.leading.equalToSuperview().offset(20) - make.width.height.equalTo(30) - make.top.greaterThanOrEqualToSuperview().offset(10) - make.bottom.lessThanOrEqualToSuperview().offset(-10) - } - - checkImageView.snp.makeConstraints { make in - make.centerY.equalToSuperview() - make.trailing.equalToSuperview().offset(-20) - } - - folderNameLabel.snp.makeConstraints { make in - make.centerY.equalToSuperview() - make.leading.equalTo(folderImageView.snp.trailing).offset(10) - make.trailing.equalTo(checkImageView.snp.leading).offset(-10) - make.top.greaterThanOrEqualToSuperview().offset(10) - make.bottom.lessThanOrEqualToSuperview().offset(-10) - } - } - - func configureWith(folder: Folder, isSelected: Bool) { - folderNameLabel.text = folder.name - checkImageView.isHidden = !isSelected - } - -} diff --git a/iBox/Sources/AddBookmark/FolderListView.swift b/iBox/Sources/AddBookmark/FolderListView.swift deleted file mode 100644 index 9ea7286..0000000 --- a/iBox/Sources/AddBookmark/FolderListView.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// FolderListView.swift -// iBox -// -// Created by μ΅œμ’…μ› on 3/7/24. -// - -import UIKit - -protocol FolderListViewDelegate: AnyObject { - func selectFolder(_ folder: Folder, at index: Int) -} - -class FolderListView: UIView { - weak var delegate: FolderListViewDelegate? - - let coreDataManager = CoreDataManager.shared - var folders: [Folder] = [] - var selectedFolderId: UUID? - - // MARK: - UI Components - - private let infoLabel = UILabel().then { - $0.text = "μƒˆλ‘œμš΄ 뢁마크λ₯Ό μΆ”κ°€ν•  폴더λ₯Ό μ„ νƒν•΄μ£Όμ„Έμš”." - $0.font = .barItemFont - $0.textColor = .label - $0.textAlignment = .center - } - - private let tableView = UITableView().then { - $0.backgroundColor = .clear - } - - private let stackView = UIStackView().then { - $0.axis = .vertical - $0.spacing = 20 - } - - // 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 - - func setupProperty() { - backgroundColor = .systemGroupedBackground - setupTableView() - } - - func setupHierarchy() { - addSubview(stackView) - } - - func setupLayout() { - stackView.snp.makeConstraints { make in - make.top.equalTo(self.safeAreaLayoutGuide.snp.top).offset(20) // Adjust as necessary - make.bottom.equalTo(self.safeAreaLayoutGuide.snp.bottom) - make.leading.equalTo(self.snp.leading) - make.trailing.equalTo(self.snp.trailing) - } - - stackView.addArrangedSubview(infoLabel) - stackView.addArrangedSubview(tableView) - } - - func setupTableView() { - self.tableView.dataSource = self - self.tableView.delegate = self - self.tableView.register(FolderListCell.self, forCellReuseIdentifier: FolderListCell.reuseIdentifier) - } - - func reloadFolderList() { - tableView.reloadData() - } -} - -extension FolderListView: UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return folders.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: FolderListCell.reuseIdentifier, for: indexPath) as! FolderListCell - let folder = folders[indexPath.row] - - let isSelectedFolder = selectedFolderId == folder.id - cell.configureWith(folder: folder, isSelected: isSelectedFolder) - - return cell - } -} - -extension FolderListView: UITableViewDelegate { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let selectedFolder = folders[indexPath.row] - UserDefaultsManager.selectedFolderId = selectedFolder.id - delegate?.selectFolder(selectedFolder, at: indexPath.row) - } -} diff --git a/iBox/Sources/AddBookmark/FolderListViewController.swift b/iBox/Sources/AddBookmark/FolderListViewController.swift deleted file mode 100644 index 338b853..0000000 --- a/iBox/Sources/AddBookmark/FolderListViewController.swift +++ /dev/null @@ -1,113 +0,0 @@ -// -// FolderListViewController.swift -// iBox -// -// Created by μ΅œμ’…μ› on 3/7/24. -// - -import UIKit - -protocol FolderListViewControllerDelegate: AnyObject { - func selectFolder(_ folder: Folder, at index: Int) - func addFolder(_ folder: Folder) -} - -class FolderListViewController: UIViewController { - weak var delegate: FolderListViewControllerDelegate? - - let folderListView = FolderListView() - - init(folders: [Folder], selectedId: UUID?) { - super.init(nibName: nil, bundle: nil) - setupFolderListView(folders, selectedId: selectedId) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func loadView() { - view = folderListView - } - - override func viewDidLoad() { - super.viewDidLoad() - - setupNavigationBar() - navigationController?.interactivePopGestureRecognizer?.delegate = self - } - - private func setupNavigationBar() { - navigationItem.hidesBackButton = true - let backButton = UIBarButtonItem(image: UIImage(systemName: "chevron.left"), style: .plain, target: self, action: #selector(back)) - let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addFolder)) - navigationItem.leftBarButtonItem = backButton - navigationItem.rightBarButtonItem = addButton - } - - @objc private func back() { - self.navigationController?.popViewController(animated: true) - } - - @objc private func addFolder() { - let controller = UIAlertController(title: "μƒˆλ‘œμš΄ 폴더", message: "이 ν΄λ”μ˜ 이름을 μž…λ ₯ν•˜μ‹­μ‹œμ˜€.", preferredStyle: .alert) - - controller.addTextField { textField in - textField.placeholder = "폴더 이름" - textField.autocorrectionType = .no - textField.spellCheckingType = .no - } - - let cancelAction = UIAlertAction(title: "μ·¨μ†Œ", style: .cancel, handler: nil) - let addAction = UIAlertAction(title: "μΆ”κ°€", style: .default) { [unowned controller, weak self] _ in - guard let textField = controller.textFields?.first, - let folderName = textField.text, !folderName.trimmingCharacters(in: .whitespaces).isEmpty else { return } - - let newFolder = Folder(id: UUID(), name: folderName, bookmarks: []) - CoreDataManager.shared.addFolder(newFolder) - self?.folderListView.folders.append(newFolder) - - self?.folderListView.selectedFolderId = newFolder.id - UserDefaultsManager.selectedFolderId = newFolder.id - - self?.folderListView.reloadFolderList() - self?.delegate?.addFolder(newFolder) - } - - controller.addAction(cancelAction) - controller.addAction(addAction) - - NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: controller.textFields?.first, queue: .main) { notification in - if let textField = notification.object as? UITextField, - let text = textField.text, !text.trimmingCharacters(in: .whitespaces).isEmpty { - addAction.isEnabled = true - } else { - addAction.isEnabled = false - } - } - - addAction.isEnabled = false - - present(controller, animated: true) - } - - private func setupFolderListView(_ folders: [Folder], selectedId: UUID?) { - folderListView.delegate = self - folderListView.folders = folders - folderListView.selectedFolderId = selectedId - } -} - -extension FolderListViewController: FolderListViewDelegate { - func selectFolder(_ folder: Folder, at index: Int) { - delegate?.selectFolder(folder, at: index) - self.navigationController?.popViewController(animated: true) - } - -} - -extension FolderListViewController: UIGestureRecognizerDelegate { - func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - return true - } -} diff --git a/iBox/Sources/AppDelegate.swift b/iBox/Sources/AppDelegate.swift index f573b9f..1d9e30e 100644 --- a/iBox/Sources/AppDelegate.swift +++ b/iBox/Sources/AppDelegate.swift @@ -5,28 +5,18 @@ // Created by 김찬희 on 2023/12/21. // -import CoreData import UIKit -import WebKit +import CoreData @main class AppDelegate: UIResponder, UIApplicationDelegate { - let versioningHandler: VersioningHandler = VersioningHandler() - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - workaroundInitialWebViewDelay() - versioningHandler.checkAppVersion { result in - AppStateManager.shared.versionCheckCompleted = result - } - + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. return true } - - func workaroundInitialWebViewDelay() { - let webView = WKWebView() - webView.loadHTMLString("", baseURL: nil) - } // MARK: UISceneSession Lifecycle @@ -42,5 +32,50 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } + // MARK: - Core Data stack + + lazy var persistentContainer: NSPersistentContainer = { + /* + The persistent container for the application. This implementation + creates and returns a container, having loaded the store for the + application to it. This property is optional since there are legitimate + error conditions that could cause the creation of the store to fail. + */ + let container = NSPersistentContainer(name: "iBox") + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + return container + }() + + // MARK: - Core Data Saving support + + func saveContext () { + let context = persistentContainer.viewContext + if context.hasChanges { + do { + try context.save() + } catch { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + let nserror = error as NSError + fatalError("Unresolved error \(nserror), \(nserror.userInfo)") + } + } } +} + diff --git a/iBox/Sources/Base/BaseViewController.swift b/iBox/Sources/Base/BaseViewController.swift deleted file mode 100644 index b0a872f..0000000 --- a/iBox/Sources/Base/BaseViewController.swift +++ /dev/null @@ -1,238 +0,0 @@ -// -// BaseViewController.swift -// iBox -// -// Created by jiyeon on 1/3/24. -// - -import UIKit - -import SnapKit - -class NavigationBar: UIView { - var backButton = UIButton() - var titleLabel = UILabel() - var addButton = UIButton() - var moreButton = UIButton() - var doneButton = UIButton() -} - -protocol BaseViewControllerProtocol { - func setupNavigationBar() -} - -class BaseViewController: UIViewController, UIGestureRecognizerDelegate { - - let backgroundColor: UIColor = .backgroundColor - let tintColor: UIColor = .label - let titleFont: UIFont = .titleFont - - // MARK: - UI Components - - let statusBar = UIView() - - let navigationBar = NavigationBar().then { - $0.backButton.configuration = .plain() - $0.backButton.configuration?.image = UIImage(systemName: "chevron.left") - $0.backButton.configuration?.preferredSymbolConfigurationForImage = .init(weight: .semibold) - $0.addButton.configuration = .plain() - $0.addButton.configuration?.image = UIImage(systemName: "plus") - $0.addButton.configuration?.preferredSymbolConfigurationForImage = .init(weight: .semibold) - $0.moreButton.configuration = .plain() - $0.moreButton.configuration?.image = UIImage(systemName: "ellipsis.circle") - $0.moreButton.configuration?.preferredSymbolConfigurationForImage = .init(weight: .semibold) - $0.doneButton.configuration = .plain() - $0.doneButton.configuration?.baseForegroundColor = .label - $0.doneButton.configuration?.attributedTitle = AttributedString("μ™„λ£Œ", attributes: AttributeContainer([NSAttributedString.Key.font: UIFont.semiboldLabelFont])) - } - - let contentView: UIView = View() - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupHierarchy() - setupLayout() - setupProperty() - } - - // MARK: - Setup Methods - - private func setupProperty() { - view.backgroundColor = .backgroundColor - navigationController?.setNavigationBarHidden(true, animated: false) - navigationController?.interactivePopGestureRecognizer?.delegate = self - - setNavigationBarTintColor(tintColor) - setNavigationBarTitleLabelFont(titleFont) - setNavigationBarBackButtonHidden(true) - setNavigationBarMenuButtonHidden(true) - setNavigationBarDoneButtonHidden(true) - - navigationBar.backButton.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside) - } - - private func setupHierarchy() { - view.addSubview(statusBar) - view.addSubview(navigationBar) - navigationBar.addSubview(navigationBar.backButton) - navigationBar.addSubview(navigationBar.titleLabel) - navigationBar.addSubview(navigationBar.addButton) - navigationBar.addSubview(navigationBar.moreButton) - navigationBar.addSubview(navigationBar.doneButton) - view.addSubview(contentView) - } - - private func setupLayout() { - statusBar.snp.makeConstraints { make in - make.leading.top.trailing.equalToSuperview() - make.bottom.equalTo(view.safeAreaLayoutGuide.snp.top) - } - - navigationBar.snp.makeConstraints { make in - make.top.equalTo(statusBar.snp.bottom) - make.leading.trailing.equalToSuperview() - make.height.equalTo(60) - } - - navigationBar.backButton.snp.makeConstraints { make in - make.leading.equalToSuperview().inset(20) - make.centerY.equalToSuperview() - make.width.height.equalTo(24) - } - - navigationBar.titleLabel.snp.makeConstraints { make in - make.center.equalToSuperview() - } - - navigationBar.moreButton.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(20) - make.centerY.equalToSuperview() - make.width.height.equalTo(24) - } - - navigationBar.addButton.snp.makeConstraints { make in - make.trailing.equalTo(navigationBar.moreButton.snp.leading).offset(-20) - make.centerY.equalToSuperview() - make.width.height.equalTo(24) - } - - navigationBar.doneButton.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(20) - make.centerY.equalToSuperview() - } - - contentView.snp.makeConstraints { make in - make.top.equalTo(statusBar.snp.bottom).offset(60) - make.leading.trailing.equalToSuperview() - make.bottom.equalToSuperview().inset(tabBarController?.tabBar.frame.height ?? 0) - } - } - - // MARK: - BaseViewController - - func setNavigationBarBackgroundColor(_ color: UIColor?) { - statusBar.backgroundColor = color - navigationBar.backgroundColor = color - } - - func setNavigationBarTintColor(_ color: UIColor) { - navigationBar.backButton.tintColor = color - navigationBar.addButton.tintColor = color - navigationBar.moreButton.tintColor = color - } - - func setNavigationBarHidden(_ hidden: Bool) { - navigationBar.isHidden = hidden - - if hidden { - contentView.snp.remakeConstraints { make in - make.top.equalTo(statusBar.snp.bottom) - make.leading.trailing.equalTo(view.safeAreaLayoutGuide) - make.bottom.equalToSuperview() - } - } else { - contentView.snp.remakeConstraints { make in - make.top.equalTo(statusBar.snp.bottom).offset(60) - make.leading.trailing.equalTo(view.safeAreaLayoutGuide) - make.bottom.equalToSuperview() - } - } - } - - func setNavigationBarBackButtonHidden(_ hidden: Bool) { - navigationBar.backButton.isHidden = hidden - - if hidden { - navigationBar.titleLabel.snp.remakeConstraints { make in - make.leading.equalToSuperview().inset(30) - make.centerY.equalToSuperview() - } - } else { - navigationBar.titleLabel.snp.remakeConstraints { make in - make.center.equalToSuperview() - } - } - } - - func setNavigationBarMenuButtonHidden(_ hidden: Bool) { - navigationBar.addButton.isHidden = hidden - navigationBar.moreButton.isHidden = hidden - } - - func setNavigationBarAddButtonHidden(_ hidden: Bool) { - navigationBar.addButton.isHidden = hidden - - if !hidden { - navigationBar.addButton.snp.remakeConstraints { make in - make.trailing.equalToSuperview().inset(20) - make.centerY.equalToSuperview() - make.width.height.equalTo(24) - } - } - } - - func setNavigationBarDoneButtonHidden(_ hidden: Bool) { - navigationBar.doneButton.isHidden = hidden - } - - func setNavigationBarAddButtonAction(_ selector: Selector) { - navigationBar.addButton.addTarget(self, action: selector, for: .touchUpInside) - } - - func setNavigationBarMoreButtonAction(_ selector: Selector) { - navigationBar.moreButton.addTarget(self, action: selector, for: .touchUpInside) - } - - func setNavigationBarDoneButtonAction(_ selector: Selector) { - navigationBar.doneButton.addTarget(self, action: selector, for: .touchUpInside) - } - - func setNavigationBarTitleLabelText(_ text: String?) { - navigationBar.titleLabel.text = text - } - - func setNavigationBarTitleLabelFont(_ font: UIFont?) { - navigationBar.titleLabel.font = font - } - - func setNavigationBarTitleLabelTextColor(_ color: UIColor?) { - navigationBar.titleLabel.textColor = color - } - - // MARK: - Action Functions - - @objc func backButtonTapped() { - navigationController?.popViewController(animated: true) - } - - // MARK: - UIGestureRecognizerDelegate - - func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - guard let navigationController = navigationController else { return false } - // Navigation Stack에 μŒ“μΈ λ·°κ°€ 1개λ₯Ό μ΄ˆκ³Όν•  λ•Œ μŠ€μ™€μ΄ν”„ 제슀처 ν—ˆμš© - return navigationController.viewControllers.count > 1 - } - -} diff --git a/iBox/Sources/Base/BottomSheetViewController.swift b/iBox/Sources/Base/BottomSheetViewController.swift deleted file mode 100644 index 97be2e3..0000000 --- a/iBox/Sources/Base/BottomSheetViewController.swift +++ /dev/null @@ -1,143 +0,0 @@ -// -// BottomSheetViewController.swift -// iBox -// -// Created by jiyeon on 1/5/24. -// - -import UIKit - -import SnapKit - -class BottomSheetViewController: UIViewController { - - var bottomSheetHeight: CGFloat - - // MARK: - UI Components - - let dimmedView = UIView().then { - $0.backgroundColor = .clear - $0.isUserInteractionEnabled = true - } - - let sheetView = View().then { - $0.clipsToBounds = true - $0.layer.cornerRadius = 30 - $0.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] // μ™Όμͺ½ μœ„, 였λ₯Έμͺ½ μœ„ λ‘₯κΈ€κ²Œ - } - - let indicator = UIView().then { - $0.clipsToBounds = true - $0.layer.cornerRadius = 2 - $0.backgroundColor = .darkGray - } - - // MARK: - Initializer - - init(bottomSheetHeight: CGFloat) { - self.bottomSheetHeight = bottomSheetHeight - super.init(nibName: nil, bundle: nil) - modalPresentationStyle = .overFullScreen - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupProperty() - setupHierarchy() - setupLayout() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - showBottomSheets() - } - - // MARK: - Setup Methods - - private func setupProperty() { - // TapGesture - let dimmedTap = UITapGestureRecognizer(target: self, action: #selector(dimmedViewTapped)) - dimmedView.addGestureRecognizer(dimmedTap) - - // SwipeGesture - let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(panGesture)) - swipeGesture.direction = .down - view.addGestureRecognizer(swipeGesture) - } - - private func setupHierarchy() { - view.addSubview(dimmedView) - view.addSubview(sheetView) - sheetView.addSubview(indicator) - } - - private func setupLayout() { - dimmedView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - - sheetView.snp.makeConstraints { make in - make.top.equalTo(view.snp.bottom) - make.leading.trailing.equalToSuperview() - make.height.equalTo(bottomSheetHeight) - } - - indicator.snp.makeConstraints { make in - make.width.equalTo(40) - make.height.equalTo(4) - make.top.equalToSuperview().inset(10) - make.centerX.equalToSuperview() - } - } - - // MARK: - Action Functions - - @objc private func dimmedViewTapped(_ tapRecognizer: UITapGestureRecognizer) { - hideBottomSheets() - } - - @objc private func panGesture(_ recognizer: UISwipeGestureRecognizer) { - if recognizer.state == .ended { - switch recognizer.direction { - case .down: hideBottomSheets() - default: break - } - } - } - - // MARK: - Bottom Sheet Action - - private func showBottomSheets() { - sheetView.snp.remakeConstraints { make in - make.leading.bottom.trailing.equalToSuperview() - make.height.equalTo(bottomSheetHeight) - } - UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn, animations: { - self.dimmedView.backgroundColor = .dimmedViewColor - self.view.layoutIfNeeded() - }) - } - - private func hideBottomSheets() { - sheetView.snp.remakeConstraints { make in - make.top.equalTo(view.snp.bottom) - make.leading.trailing.equalToSuperview() - make.height.equalTo(bottomSheetHeight) - } - UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn, animations: { - self.dimmedView.backgroundColor = .clear - self.view.layoutIfNeeded() - }) { _ in - if self.presentingViewController != nil { - self.dismiss(animated: false) - } - } - } - -} diff --git a/iBox/Sources/BaseViewController.swift b/iBox/Sources/BaseViewController.swift new file mode 100644 index 0000000..fd38cb5 --- /dev/null +++ b/iBox/Sources/BaseViewController.swift @@ -0,0 +1,18 @@ +// +// BaseViewController.swift +// iBox +// +// Created by jiyeon on 12/26/23. +// + +import UIKit + +class BaseViewController: UIViewController { + + let baseView = View(frame: UIScreen.main.bounds) + + override func viewDidLoad() { + super.viewDidLoad() + view.addSubview(baseView) + } +} diff --git a/iBox/Sources/BoxList/BoxListCell.swift b/iBox/Sources/BoxList/BoxListCell.swift deleted file mode 100644 index b9929b4..0000000 --- a/iBox/Sources/BoxList/BoxListCell.swift +++ /dev/null @@ -1,129 +0,0 @@ -// -// BoxListCell.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/30/24. -// - -import UIKit - -import SnapKit - -class BoxListCell: UITableViewCell { - var viewModel: BoxListCellViewModel? - static let reuseIdentifier = "boxListCell" - - var onDelete: (() -> Void)? - var onEdit: (() -> Void)? - - // MARK: - UI Components - - private let cellImageView = UIImageView().then { - $0.image = UIImage(systemName: "ellipsis.rectangle.fill") - $0.tintColor = .label - $0.contentMode = .scaleAspectFit - } - - private let label = UILabel().then { - $0.font = .cellTitleFont - } - - private let editButton = UIButton().then{ - $0.configuration = .plain() - $0.configuration?.image = UIImage(systemName: "ellipsis.circle")?.withTintColor(.box, renderingMode: .alwaysOriginal) - - $0.showsMenuAsPrimaryAction = true - $0.isHidden = true - } - - private lazy var editAction = UIAction(title: "뢁마크 νŽΈμ§‘", image: UIImage(systemName: "pencil")) {[weak self] _ in - self?.onEdit?() - } - - private lazy var deleteAction = UIAction(title: "μ‚­μ œ", image: UIImage(systemName: "trash"), attributes: .destructive) { [weak self] _ in - self?.onDelete?() - } - - // MARK: - Initializer - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupProperty() - setupHierarchy() - setupLayout() - configureEditMenu() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func prepareForReuse() { - viewModel = nil - onDelete = nil - onEdit = nil - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .tableViewBackgroundColor - selectionStyle = .gray - } - - private func setupHierarchy() { - contentView.addSubview(cellImageView) - contentView.addSubview(label) - contentView.addSubview(editButton) - } - - private func setupLayout() { - cellImageView.snp.makeConstraints { make in - make.leading.equalToSuperview().inset(25) - make.top.bottom.equalToSuperview().inset(10) - make.width.equalTo(23) - } - - label.snp.makeConstraints { make in - make.top.bottom.equalToSuperview() - make.trailing.equalToSuperview().inset(25) - make.leading.equalTo(cellImageView.snp.trailing).offset(8) - } - - editButton.snp.makeConstraints { make in - make.width.equalTo(25) - make.top.bottom.equalToSuperview() - make.trailing.equalToSuperview().offset(-10) - } - } - - private func configureEditMenu() { - editButton.menu = UIMenu(options: .displayInline, children: [editAction, deleteAction]) - } - - // MARK: - Bind ViewModel - - func bindViewModel(_ viewModel: BoxListCellViewModel) { - self.viewModel = viewModel - label.text = viewModel.name - } - - func setEditButtonHidden(_ isHidden: Bool) { - editButton.isHidden = isHidden - - if isHidden { - label.snp.remakeConstraints { make in - make.top.bottom.equalToSuperview() - make.trailing.equalToSuperview().inset(25) - make.leading.equalTo(cellImageView.snp.trailing).offset(10) - } - } else { - label.snp.remakeConstraints { make in - make.top.bottom.equalToSuperview() - make.trailing.equalTo(editButton.snp.leading).offset(-5) - make.leading.equalTo(cellImageView.snp.trailing).offset(10) - } - } - } - -} diff --git a/iBox/Sources/BoxList/BoxListCellViewModel.swift b/iBox/Sources/BoxList/BoxListCellViewModel.swift deleted file mode 100644 index 2c6cefc..0000000 --- a/iBox/Sources/BoxList/BoxListCellViewModel.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// BoxListCellViewModel.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/30/24. -// - -import Foundation - -class BoxListCellViewModel: Identifiable { - var bookmark: Bookmark - - init(bookmark: Bookmark) { - self.bookmark = bookmark - } - - var id: UUID { - bookmark.id - } - - var name: String { - bookmark.name - } - - var url: URL { - bookmark.url - } - -} diff --git a/iBox/Sources/BoxList/BoxListSectionViewModel.swift b/iBox/Sources/BoxList/BoxListSectionViewModel.swift deleted file mode 100644 index bb2d0e9..0000000 --- a/iBox/Sources/BoxList/BoxListSectionViewModel.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// BoxListSectionViewModel.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/30/24. -// - -import Foundation - -class BoxListSectionViewModel: Identifiable { - var folder: Folder - - var boxListCellViewModels: [BoxListCellViewModel] { - didSet { - folder.bookmarks = boxListCellViewModels.map { - Bookmark(id: $0.id, name: $0.name, url: $0.url) - } - } - } - - init(folder: Folder) { - self.folder = folder - boxListCellViewModels = folder.bookmarks.map { BoxListCellViewModel(bookmark: $0) } - } - - var id: UUID { - folder.id - } - - var name: String { - get { - folder.name - } - set { - folder.name = newValue - } - } - - var isOpened: Bool = false - - - var boxListCellViewModelsWithStatus: [BoxListCellViewModel] { - return isOpened ? boxListCellViewModels : [] - } - - func viewModel(at index: Int) -> BoxListCellViewModel { - return boxListCellViewModels[index] - } - - @discardableResult - func deleteCell(at index: Int) -> BoxListCellViewModel { - let cell = boxListCellViewModels[index] - boxListCellViewModels.remove(at: index) - return cell - } - - func updateCell(at index: Int, bookmark: Bookmark) { - boxListCellViewModels[index].bookmark = bookmark - } - - func insertCell(_ cell: BoxListCellViewModel, at index: Int) { - boxListCellViewModels.insert(cell, at: index) - } - - @discardableResult - func openSectionIfNeeded() -> Bool { - if !isOpened { - isOpened = true - return true - } - return false - } -} - diff --git a/iBox/Sources/BoxList/BoxListView.swift b/iBox/Sources/BoxList/BoxListView.swift deleted file mode 100644 index abbaba0..0000000 --- a/iBox/Sources/BoxList/BoxListView.swift +++ /dev/null @@ -1,525 +0,0 @@ -// -// BoxListView.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/3/24. -// - -import Combine -import UIKit - -import SnapKit - -protocol BoxListViewDelegate: AnyObject { - func didSelectWeb(id: UUID, at url: URL, withName name: String) - func pushViewController(type: EditType) - func pushViewController(url: URL?) - func presentEditBookmarkController(at indexPath: IndexPath) - func deleteFolderinBoxList(at section: Int) - func editFolderNameinBoxList(at section: Int, currentName: String) -} - -class BoxListView: UIView { - - var viewModel: BoxListViewModel? - private var boxListDataSource: BoxListDataSource! - weak var delegate: BoxListViewDelegate? - private var cancellables = Set() - - // MARK: - UI Components - - private let backgroundView = UIView().then { - $0.clipsToBounds = true - $0.layer.cornerRadius = 20 - $0.backgroundColor = .tableViewBackgroundColor - } - - private let tableView = UITableView(frame: .zero, style: .grouped).then { - $0.register(BoxListCell.self, forCellReuseIdentifier: BoxListCell.reuseIdentifier) - - $0.sectionHeaderTopPadding = 0 - $0.clipsToBounds = true - $0.layer.cornerRadius = 20 - $0.backgroundColor = .clear - $0.separatorColor = .clear - $0.rowHeight = 50 - $0.estimatedSectionHeaderHeight = 0 - $0.estimatedSectionFooterHeight = 0 - $0.estimatedRowHeight = 0 - } - - private let emptyStackView = UIStackView().then { - $0.axis = .horizontal - $0.spacing = 10 - $0.isHidden = true - } - - private let emptyLabel = UILabel().then { - $0.text = "폴더가 μ—†μŠ΅λ‹ˆλ‹€" - $0.font = .emptyLabelFont - $0.textColor = .secondaryLabel - $0.textAlignment = .center - } - - private lazy var lightEmptyImages = [ - UIImage(named: "sitting_fox0")?.imageWithColor(.secondaryLabel.resolvedColor(with: .init(userInterfaceStyle: .light))) ?? UIImage(), - UIImage(named: "sitting_fox1")?.imageWithColor(.secondaryLabel.resolvedColor(with: .init(userInterfaceStyle: .light))) ?? UIImage(), - UIImage(named: "sitting_fox2")?.imageWithColor(.secondaryLabel.resolvedColor(with: .init(userInterfaceStyle: .light))) ?? UIImage(), - UIImage(named: "sitting_fox3")?.imageWithColor(.secondaryLabel.resolvedColor(with: .init(userInterfaceStyle: .light))) ?? UIImage() - ] - - private lazy var darkEmptyImages = [ - UIImage(named: "sitting_fox0")?.imageWithColor(.secondaryLabel.resolvedColor(with: .init(userInterfaceStyle: .dark))) ?? UIImage(), - UIImage(named: "sitting_fox1")?.imageWithColor(.secondaryLabel.resolvedColor(with: .init(userInterfaceStyle: .dark))) ?? UIImage(), - UIImage(named: "sitting_fox2")?.imageWithColor(.secondaryLabel.resolvedColor(with: .init(userInterfaceStyle: .dark))) ?? UIImage(), - UIImage(named: "sitting_fox3")?.imageWithColor(.secondaryLabel.resolvedColor(with: .init(userInterfaceStyle: .dark))) ?? UIImage() - ] - - private let emptyImageView = UIImageView().then { - $0.contentMode = .scaleAspectFit - $0.tintColor = .secondaryLabel - $0.animationDuration = 1.5 - } - - // MARK: - Initializer - - override init(frame: CGRect) { - super.init(frame: frame) - setupProperty() - setupHierarchy() - setupLayout() - configureDataSource() - bindViewModel() - subscribeToNotifications() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - if #available(iOS 13.0, *), traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { - if previousTraitCollection?.userInterfaceStyle == .light { - emptyImageView.animationImages = darkEmptyImages - } else { - emptyImageView.animationImages = lightEmptyImages - } - if !emptyStackView.isHidden { - emptyImageView.startAnimating() - } - } - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .backgroundColor - viewModel = BoxListViewModel() - tableView.delegate = self - - if UITraitCollection.current.userInterfaceStyle == .light { - emptyImageView.animationImages = lightEmptyImages - } else { - emptyImageView.animationImages = darkEmptyImages - } - } - - private func setupHierarchy() { - addSubview(backgroundView) - backgroundView.addSubview(tableView) - backgroundView.addSubview(emptyStackView) - } - - private func setupLayout() { - backgroundView.snp.makeConstraints { make in - make.top.equalTo(safeAreaLayoutGuide).inset(10) - make.leading.trailing.bottom.equalTo(safeAreaLayoutGuide).inset(20) - } - - tableView.snp.makeConstraints { make in - make.top.bottom.equalToSuperview().offset(10) - make.leading.trailing.equalToSuperview() - } - - emptyStackView.snp.makeConstraints { make in - make.center.equalToSuperview() - } - - emptyImageView.snp.makeConstraints { make in - make.width.height.equalTo(30) - } - - emptyStackView.addArrangedSubview(emptyLabel) - emptyStackView.addArrangedSubview(emptyImageView) - - } - - private func configureDataSource() { - boxListDataSource = BoxListDataSource(tableView: tableView) { [weak self] tableView, indexPath, itemIdentifier in - guard let self, let viewModel = self.viewModel else { fatalError() } - guard let cell = tableView.dequeueReusableCell(withIdentifier: BoxListCell.reuseIdentifier, for: indexPath) as? BoxListCell else { fatalError() } - cell.setEditButtonHidden(!viewModel.isEditing) - cell.bindViewModel(viewModel.viewModel(at: indexPath)) - cell.onDelete = { [weak self, weak cell] in - guard let self = self, let cell = cell else { return } - if let currentIndexPath = self.tableView.indexPath(for: cell) { - self.viewModel?.deleteBookmark(at: currentIndexPath) - } - } - cell.onEdit = { [weak self, weak cell] in - guard let self = self, let cell = cell else { return } - if let currentIndexPath = self.tableView.indexPath(for: cell) { - self.delegate?.presentEditBookmarkController(at: currentIndexPath) - } - } - - return cell - } - boxListDataSource.defaultRowAnimation = .top - boxListDataSource.delegate = self - } - - private func applySnapshot(with sections: [BoxListSectionViewModel]) { - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections(sections.map{ $0.id }) - for section in sections { - snapshot.appendItems(section.boxListCellViewModelsWithStatus.map { $0.id }, toSection: section.id) - } - boxListDataSource.apply(snapshot, animatingDifferences: true) - } - - private func bindViewModel() { - guard let viewModel else { return } - let output = viewModel.transform(input: viewModel.input.eraseToAnyPublisher()) - - output.receive(on: DispatchQueue.main) - .sink { [weak self] event in - switch event { - case .sendBoxList(boxList: let boxList): - self?.applySnapshot(with: boxList) - self?.emptyStackView.isHidden = !boxList.isEmpty - if self?.emptyStackView.isHidden == false { - self?.emptyImageView.startAnimating() - } else { - self?.emptyImageView.stopAnimating() - } - case .editStatus(isEditing: let isEditing): - self?.tableView.setEditing(isEditing, animated: false) - guard let snapshot = self?.boxListDataSource.snapshot() else { return } - self?.boxListDataSource.applySnapshotUsingReloadData(snapshot) - case .reloadSections(idArray: let idArray): - guard var snapshot = self?.boxListDataSource.snapshot() else { return } - snapshot.reloadSections(idArray) - self?.boxListDataSource.apply(snapshot, animatingDifferences: false) - case .reloadRows(idArray: let idArray): - guard var snapshot = self?.boxListDataSource.snapshot() else { return } - snapshot.reloadItems(idArray) - self?.boxListDataSource.apply(snapshot) - case .openCloseFolder(boxList: let boxList, section: let section, isEmpty: let isEmpty): - self?.applySnapshot(with: boxList) - self?.tableView.layoutIfNeeded() - if !isEmpty { - let indexPath = IndexPath(row: NSNotFound, section: section) - self?.tableView.scrollToRow(at: indexPath, at: .top, animated: true) - } - } - }.store(in: &cancellables) - } - - private func subscribeToNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(dataDidReset), name: .didResetData, object: nil) - } - - @objc private func dataDidReset(notification: NSNotification) { - viewModel?.input.send(.viewDidLoad) - } - -} - -extension BoxListView: UITableViewDelegate { - - public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { - let view = UIView() - let line = UIView() - view.addSubview(line) - line.snp.makeConstraints { make in - make.top.bottom.equalToSuperview() - make.leading.trailing.equalToSuperview().inset(15) - } - view.backgroundColor = .tableViewBackgroundColor - line.backgroundColor = .quaternaryLabel - return view - } - - public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { - return 0.7 - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return 50 - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 48 - } - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - guard let viewModel else { return nil } - let button = FolderButton(isOpen: viewModel.boxList[section].isOpened) - button.setFolderName(viewModel.boxList[section].name) - button.tag = section - - // ν„°μΉ˜ν–ˆμ„ λ•Œ - button.addTarget(self, action: #selector(handleOpenClose), for: .touchUpInside) - // 길게 λˆŒλ €μ„ λ•Œ - button.addTarget(self, action: #selector(handleMenu), for: .menuActionTriggered) - - let edit = UIAction(title: "폴더 νŽΈμ§‘", image: UIImage(systemName: "pencil")) { [weak self] _ in - guard let folderName = self?.viewModel?.boxList[section].name else { return } - self?.delegate?.editFolderNameinBoxList(at: section, currentName: folderName) - } - let delete = UIAction(title: "폴더 μ‚­μ œ", image: UIImage(systemName: "trash"), attributes: .destructive) { [weak self] _ in - self?.delegate?.deleteFolderinBoxList(at: section) - } - - button.menu = UIMenu(options: .displayInline, children: [edit, delete]) - - return button - } - - @objc private func handleOpenClose(button: FolderButton) { - guard let viewModel else { return } - viewModel.input.send(.folderTapped(section: button.tag)) - button.toggleStatus() - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .light) - generator.prepare() - generator.impactOccurred() - } - } - - @objc private func handleMenu(button: FolderButton) { - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .medium) - generator.prepare() - generator.impactOccurred() - } - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let cellViewModel = viewModel?.boxList[indexPath.section].boxListCellViewModelsWithStatus[indexPath.row] else { return } - delegate?.didSelectWeb(id: cellViewModel.id, at: cellViewModel.url, withName: cellViewModel.name) - - tableView.deselectRow(at: indexPath, animated: true) - } - - func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - // μ•‘μ…˜ μ •μ˜ - let favoriteAction = UIContextualAction(style: .normal, title: "favorite", handler: { [weak self] (action, view, completionHandler) in - self?.viewModel?.input.send(.toggleFavorite(indexPath: indexPath)) - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .soft) - generator.prepare() - generator.impactOccurred() - } - completionHandler(true) - }) - favoriteAction.backgroundColor = .box2 - if viewModel?.isFavoriteBookmark(at: indexPath) == true { - favoriteAction.image = UIImage(systemName: "heart.fill") - } else { - favoriteAction.image = UIImage(systemName: "heart") - } - - let shareAction = UIContextualAction(style: .normal, title: "share", handler: {(action, view, completionHandler) in - let cellViewModel = self.viewModel?.viewModel(at: indexPath) - self.delegate?.pushViewController(url: cellViewModel?.url) - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .soft) - generator.prepare() - generator.impactOccurred() - } - completionHandler(true) - }) - shareAction.backgroundColor = .box3 - shareAction.image = UIImage(systemName: "square.and.arrow.up") - - let deleteAction = UIContextualAction(style: .normal, title: "delete", handler: {(action, view, completionHandler) in - self.viewModel?.input.send(.deleteBookmark(indexPath: indexPath)) - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .soft) - generator.prepare() - generator.impactOccurred() - } - completionHandler(true) - }) - deleteAction.backgroundColor = .systemGray - deleteAction.image = UIImage(systemName: "trash.fill") - - // μŠ€μ™€μ΄ν”„ μ•‘μ…˜ ꡬ성 - let configuration = UISwipeActionsConfiguration(actions: [deleteAction, shareAction, favoriteAction]) - configuration.performsFirstActionWithFullSwipe = false // μ™„μ „νžˆ μŠ€μ™€μ΄ν”„ν–ˆμ„ λ•Œ 첫 번째 μ•‘μ…˜μ΄ μžλ™μœΌλ‘œ μ‹€ν–‰λ˜λŠ” 것을 λ§‰μŒ - - return configuration - } - - func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { - return .none - } - - func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { - return false - } - - func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let configuration = UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: { [weak self] () -> UIViewController? in - guard let self = self else { return nil } - return self.createOrRetrievePreviewController(for: indexPath) - }, actionProvider: { suggestedActions in - return self.makeContextMenu(for: indexPath) - }) - return configuration - } - - private func createOrRetrievePreviewController(for indexPath: IndexPath) -> UIViewController? { - guard let cellViewModel = self.viewModel?.boxList[indexPath.section].boxListCellViewModelsWithStatus[indexPath.row] else { return nil } - let id = cellViewModel.id - let cachedViewController = WebCacheManager.shared.viewControllerForKey(id) - - if let cachedViewController = cachedViewController, cachedViewController.errorViewController?.isHandlingError == nil { - return cachedViewController - } - - if cachedViewController?.errorViewController?.isHandlingError ?? false { - WebCacheManager.shared.removeViewControllerForKey(id) - } - - let newViewController = createWebViewController(with: cellViewModel) - WebCacheManager.shared.cacheData(forKey: id, viewController: newViewController) - return newViewController - } - - private func createWebViewController(with viewModel: BoxListCellViewModel) -> WebViewController { - let viewController = WebViewController() - viewController.selectedWebsite = viewModel.url - viewController.title = viewModel.name - viewController.id = viewModel.id - return viewController - } - - func tableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { - guard let indexPath = configuration.identifier as? IndexPath, let cell = tableView.cellForRow(at: indexPath) else { - return nil - } - - let parameters = UIPreviewParameters() - parameters.backgroundColor = .clear - - return UITargetedPreview(view: cell, parameters: parameters) - } - - private func makeContextMenu(for indexPath: IndexPath) -> UIMenu { - let isFavorite = self.viewModel?.isFavoriteBookmark(at: indexPath) ?? false - let favoriteActionTitle = isFavorite ? "즐겨찾기 ν•΄μ œ" : "즐겨찾기둜 등둝" - let favoriteActionImage = UIImage(systemName: isFavorite ? "heart.slash.fill" : "heart.fill") - - let favoriteAction = UIAction(title: favoriteActionTitle, image: favoriteActionImage) { [weak self] action in - self?.viewModel?.input.send(.toggleFavorite(indexPath: indexPath)) - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .soft) - generator.prepare() - generator.impactOccurred() - } - } - - favoriteAction.image?.withTintColor(.box2) - - let shareAction = UIAction(title: "κ³΅μœ ν•˜κΈ°", image: UIImage(systemName: "square.and.arrow.up")) { [weak self] action in - guard let self = self, let url = self.viewModel?.boxList[indexPath.section].boxListCellViewModelsWithStatus[indexPath.row].url else { return } - - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .soft) - generator.prepare() - generator.impactOccurred() - } - - let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) - if let viewController = self.delegate as? UIViewController { - viewController.present(activityViewController, animated: true, completion: nil) - } - } - - let editAction = UIAction(title: "뢁마크 νŽΈμ§‘", image: UIImage(systemName: "pencil")) { [weak self] action in - guard let self = self else { return } - - self.delegate?.presentEditBookmarkController(at: indexPath) - - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .soft) - generator.prepare() - generator.impactOccurred() - } - } - - let deleteAction = UIAction(title: "μ‚­μ œ", image: UIImage(systemName: "trash"), attributes: .destructive) { [weak self] action in - self?.viewModel?.input.send(.deleteBookmark(indexPath: indexPath)) - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .soft) - generator.prepare() - generator.impactOccurred() - } - } - - - - return UIMenu(title: "", children: [favoriteAction, shareAction, editAction, deleteAction]) - } -} - -extension BoxListView: BoxListDataSourceDelegate { - func openFolderIfNeeded(_ folderIndex: Int) { - viewModel?.input.send(.openFolderIfNeeded(folderIndex: folderIndex)) - } - - func moveCell(at sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { - viewModel?.input.send(.moveBookmark(from: sourceIndexPath, to: destinationIndexPath)) - } - -} - -protocol BoxListDataSourceDelegate: AnyObject { - func moveCell(at sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) - func openFolderIfNeeded(_ folderIndex: Int) -} - -class BoxListDataSource: UITableViewDiffableDataSource { - weak var delegate: BoxListDataSourceDelegate? - - override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { - delegate?.moveCell(at: sourceIndexPath, to: destinationIndexPath) - - guard let src = itemIdentifier(for: sourceIndexPath), - sourceIndexPath != destinationIndexPath else { return } - - var snap = snapshot() - if let dest = itemIdentifier(for: destinationIndexPath) { - if sourceIndexPath.section == destinationIndexPath.section && sourceIndexPath.row < destinationIndexPath.row { - snap.moveItem(src, afterItem: dest) - } else { - snap.moveItem(src, beforeItem:dest) - } - } else { - snap.deleteItems([src]) - snap.appendItems([src], toSection: snap.sectionIdentifiers[destinationIndexPath.section]) - } - - self.apply(snap, animatingDifferences: false) - - delegate?.openFolderIfNeeded(destinationIndexPath.section) - } -} diff --git a/iBox/Sources/BoxList/BoxListViewController.swift b/iBox/Sources/BoxList/BoxListViewController.swift deleted file mode 100644 index 5c95f7b..0000000 --- a/iBox/Sources/BoxList/BoxListViewController.swift +++ /dev/null @@ -1,303 +0,0 @@ -// -// BoxListViewController.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 12/27/23. -// - -import UIKit - -import SkeletonView - -class BoxListViewController: BaseViewController, BaseViewControllerProtocol { - - var shouldPresentModalAutomatically: Bool = false { - didSet { - if shouldPresentModalAutomatically { - if let vc = findAddBookmarkViewController() { - if vc.presentedViewController is UIAlertController { - vc.dismiss(animated: false) - } - } else { - dismiss(animated: false) - self.addButtonTapped() - } - shouldPresentModalAutomatically = false - } - } - } - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupNavigationBar() - - guard let contentView = contentView as? BoxListView else { return } - contentView.viewModel?.input.send(.viewDidLoad) - contentView.delegate = self - } - - override func viewWillAppear(_ animated: Bool) { - guard let contentView = contentView as? BoxListView else { return } - contentView.viewModel?.input.send(.viewWillAppear) - } - - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - coordinator.animate(alongsideTransition: nil) { _ in - self.dismissPreviewIfNeeded() - } - } - - func dismissPreviewIfNeeded() { - if let previewVC = self.presentedViewController as? WebViewController { - previewVC.dismiss(animated: true, completion: nil) - } - } - - // MARK: - BaseViewControllerProtocol - - func setupNavigationBar() { - setNavigationBarTitleLabelText("42Box") - setNavigationBarMenuButtonHidden(false) - setNavigationBarAddButtonAction(#selector(addButtonTapped)) - setNavigationBarMoreButtonAction(#selector(moreButtonTapped)) - setNavigationBarDoneButtonAction(#selector(doneButtonTapped)) - } - - // MARK: - Action Functions - - @objc private func addButtonTapped() { - let addBookmarkViewController = AddBookmarkViewController() - addBookmarkViewController.delegate = self - - let navigationController = UINavigationController(rootViewController: addBookmarkViewController) - - navigationController.modalPresentationStyle = .pageSheet - present(navigationController, animated: true, completion: nil) - } - - @objc private func moreButtonTapped() { - let editViewController = EditViewController(bottomSheetHeight: 200) - editViewController.delegate = self - present(editViewController, animated: false) - } - - @objc private func doneButtonTapped() { - guard let contentView = contentView as? BoxListView else { return } - contentView.viewModel?.input.send(.toggleEditStatus) - setNavigationBarMenuButtonHidden(false) - setNavigationBarDoneButtonHidden(true) - } - -} - -extension BoxListViewController: AddBookmarkViewControllerProtocol { - func addFolderDirect(_ folder: Folder) { - guard let contentView = contentView as? BoxListView else { return } - contentView.viewModel?.addFolderDirect(folder) - } - - func addBookmarkDirect(_ bookmark: Bookmark, at folderIndex: Int) { - guard let contentView = contentView as? BoxListView else { return } - contentView.viewModel?.addBookmarkDirect(bookmark, at: folderIndex) - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .soft) - generator.prepare() - generator.impactOccurred() - } - } - -} - -extension BoxListViewController: BoxListViewDelegate { - func deleteFolderinBoxList(at section: Int) { - recheckDeleteFolder(at: section) - } - - private func recheckDeleteFolder(at section: Int) { - let actionSheetController = UIAlertController(title: nil, message: "λͺ¨λ“  λΆλ§ˆν¬κ°€ μ‚­μ œλ©λ‹ˆλ‹€.", preferredStyle: .alert) - let firstAction = UIAlertAction(title: "폴더 μ‚­μ œ", style: .destructive) {[weak self] _ in - guard let contentView = self?.contentView as? BoxListView else { return } - contentView.viewModel?.deleteFolderDirect(section) - } - let cancelAction = UIAlertAction(title: "μ·¨μ†Œ", style: .cancel) - actionSheetController.addAction(firstAction) - actionSheetController.addAction(cancelAction) - present(actionSheetController, animated: true) - } - - func editFolderNameinBoxList(at section: Int, currentName: String) { - let controller = UIAlertController(title: "폴더 이름 λ³€κ²½", message: nil, preferredStyle: .alert) - - let cancelAction = UIAlertAction(title: "μ·¨μ†Œ", style: .default) { _ in return } - let okAction = UIAlertAction(title: "확인", style: .default) { [weak self] action in - guard let newName = controller.textFields?.first?.text else { return } - guard let contentView = self?.contentView as? BoxListView else { return } - contentView.viewModel?.editFolderDirect(section, name: newName) - } - controller.addAction(cancelAction) - controller.addAction(okAction) - okAction.isEnabled = true - - controller.addTextField() { textField in - NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: textField, queue: OperationQueue.main, using: - {_ in - let textCount = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines).count ?? 0 - let textIsNotEmpty = textCount > 0 - - okAction.isEnabled = textIsNotEmpty - - }) - } - controller.textFields?.first?.text = currentName - controller.textFields?.first?.autocorrectionType = .no - controller.textFields?.first?.spellCheckingType = .no - - self.present(controller, animated: true) - } - - func presentEditBookmarkController(at indexPath: IndexPath) { - guard let contentView = contentView as? BoxListView else { return } - - let controller = UIAlertController(title: "뢁마크 νŽΈμ§‘", message: nil, preferredStyle: .alert) - - let cancelAction = UIAlertAction(title: "μ·¨μ†Œ", style: .default) { _ in return } - let okAction = UIAlertAction(title: "확인", style: .default) { [weak self] action in - guard let newName = controller.textFields?.first?.text else { return } - guard let newUrlString = controller.textFields?.last?.text, - let newUrl = URL(string: newUrlString) else { return } - guard let contentView = self?.contentView as? BoxListView else { return } - guard let bookmark = contentView.viewModel?.bookmark(at: indexPath) else { return } - - contentView.viewModel?.editBookmark(at: indexPath, name: newName, url: newUrl) - - WebCacheManager.shared.removeViewControllerForKey(bookmark.id) - } - - controller.addAction(cancelAction) - controller.addAction(okAction) - okAction.isEnabled = true - - controller.addTextField() { textField in - textField.clearButtonMode = .whileEditing - - NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: textField, queue: OperationQueue.main, using: - {_ in - let textCount = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines).count ?? 0 - let textIsNotEmpty = textCount > 0 - - okAction.isEnabled = textIsNotEmpty - - }) - } - controller.addTextField() { textField in - textField.clearButtonMode = .whileEditing - - NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: textField, queue: OperationQueue.main, using: - {_ in - let textCount = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines).count ?? 0 - let textIsNotEmpty = textCount > 0 - - okAction.isEnabled = textIsNotEmpty - - }) - } - - guard let bookmark = contentView.viewModel?.bookmark(at: indexPath) else { return } - - controller.textFields?.first?.text = bookmark.name - controller.textFields?.first?.autocorrectionType = .no - controller.textFields?.first?.spellCheckingType = .no - - controller.textFields?.last?.text = bookmark.url.absoluteString - controller.textFields?.last?.autocorrectionType = .no - controller.textFields?.last?.spellCheckingType = .no - - self.present(controller, animated: true) - } - - func didSelectWeb(id: UUID, at url: URL, withName name: String) { - let viewController = getOrCreateWebViewController(id: id, url: url, name: name) - navigationController?.pushViewController(viewController, animated: true) - } - - private func getOrCreateWebViewController(id: UUID, url: URL, name: String) -> WebViewController { - let cachedViewController = WebCacheManager.shared.viewControllerForKey(id) - - if let cachedViewController = cachedViewController, cachedViewController.errorViewController?.isHandlingError == nil { - return cachedViewController - } - - if cachedViewController?.errorViewController?.isHandlingError ?? false { - WebCacheManager.shared.removeViewControllerForKey(id) - } - - return createAndCacheWebViewController(id: id, url: url, name: name) - } - - private func createAndCacheWebViewController(id: UUID, url: URL, name: String) -> WebViewController { - let viewController = WebViewController() - viewController.delegate = self - viewController.selectedWebsite = url - viewController.title = name - viewController.id = id - WebCacheManager.shared.cacheData(forKey: id, viewController: viewController) - return viewController - } - - func pushViewController(type: EditType) { - guard let contentView = contentView as? BoxListView else { return } - switch type { - case .folder: - - let editFolderViewController = EditFolderViewController(folders: contentView.viewModel?.folders ?? []) - editFolderViewController.delegate = self - navigationController?.pushViewController(editFolderViewController, animated: true) - case .bookmark: - contentView.viewModel?.input.send(.toggleEditStatus) - setNavigationBarMenuButtonHidden(true) - setNavigationBarDoneButtonHidden(false) - } - } - - func pushViewController(url: URL?) { - guard let url = url else { return } - let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) - self.present(activityViewController, animated: true) - } - -} - -extension BoxListViewController: EditFolderViewControllerDelegate { - func moveFolder(from: Int, to: Int) { - guard let contentView = contentView as? BoxListView else { return } - contentView.viewModel?.moveFolder(from: from, to: to) - } - - func editFolderName(at row: Int, name: String) { - guard let contentView = contentView as? BoxListView else { return } - contentView.viewModel?.editFolderName(at: row, name: name) - } - - func deleteFolder(at row: Int) { - guard let contentView = contentView as? BoxListView else { return } - contentView.viewModel?.deleteFolder(at: row) - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .soft) - generator.prepare() - generator.impactOccurred() - } - } - - func addFolder(_ folder: Folder) { - guard let contentView = contentView as? BoxListView else { return } - contentView.viewModel?.addFolder(folder) - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .soft) - generator.prepare() - generator.impactOccurred() - } - } -} diff --git a/iBox/Sources/BoxList/BoxListViewModel.swift b/iBox/Sources/BoxList/BoxListViewModel.swift deleted file mode 100644 index adc72f7..0000000 --- a/iBox/Sources/BoxList/BoxListViewModel.swift +++ /dev/null @@ -1,206 +0,0 @@ -// -// BoxListViewModel.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/30/24. -// - -import Combine -import Foundation - -class BoxListViewModel { - - var boxList = [BoxListSectionViewModel]() - - var folders: [Folder] { - boxList.map{ $0.folder } - } - - var sectionsToReload = Set() - var rowsToReload = Set() - var isEditing = false - var favoriteId: UUID? = nil - - - enum Input { - case toggleEditStatus - case viewDidLoad - case viewWillAppear - case folderTapped(section: Int) - case deleteBookmark(indexPath: IndexPath) - case toggleFavorite(indexPath: IndexPath) - case moveBookmark(from: IndexPath, to: IndexPath) - case openFolderIfNeeded(folderIndex: Int) - } - - enum Output { - case sendBoxList(boxList: [BoxListSectionViewModel]) - case reloadSections(idArray: [BoxListSectionViewModel.ID]) - case reloadRows(idArray: [BoxListCellViewModel.ID]) - case editStatus(isEditing: Bool) - case openCloseFolder(boxList: [BoxListSectionViewModel], section: Int, isEmpty: Bool) - } - - let input = PassthroughSubject() - private let output = PassthroughSubject() - private var cancellables = Set() - - func transform(input: AnyPublisher) -> AnyPublisher { - input.sink { [weak self] event in - guard let self else { return } - switch event { - case .toggleEditStatus: - isEditing.toggle() - output.send(.editStatus(isEditing: isEditing)) - case .viewDidLoad: - let folders = CoreDataManager.shared.getFolders() - boxList = folders.map{ BoxListSectionViewModel(folder: $0) } - favoriteId = UserDefaultsManager.favoriteId - output.send(.sendBoxList(boxList: boxList)) - case .viewWillAppear: - output.send(.sendBoxList(boxList: boxList)) - if !sectionsToReload.isEmpty { - output.send(.reloadSections(idArray: Array(sectionsToReload))) - sectionsToReload.removeAll() - } - case let .folderTapped(section): - boxList[section].isOpened.toggle() - output.send(.openCloseFolder(boxList: boxList, section: section, isEmpty: boxList[section].boxListCellViewModelsWithStatus.isEmpty)) - case let .deleteBookmark(indexPath): - deleteBookmark(at: indexPath) - case let .toggleFavorite(indexPath): - toggleFavorite(at: indexPath) - case .moveBookmark(from: let from, to: let to): - reorderBookmark(srcIndexPath: from, destIndexPath: to) - case .openFolderIfNeeded(folderIndex: let folderIndex): - openFolder(folderIndex) - } - }.store(in: &cancellables) - return output.eraseToAnyPublisher() - } - - func viewModel(at indexPath: IndexPath) -> BoxListCellViewModel { - return boxList[indexPath.section].boxListCellViewModels[indexPath.row] - } - - func isFavoriteBookmark(at indexPath: IndexPath) -> Bool { - if let favoriteId { - if favoriteId == bookmark(at: indexPath).id { - return true - } else { return false } - } else { - return false - } - } - - private func toggleFavorite(at indexPath: IndexPath) { - let bookmark = boxList[indexPath.section].viewModel(at: indexPath.row) - if let prevId = favoriteId { - if prevId == bookmark.id { // μ§€κΈˆ λ“€μ–΄μ˜¨κ²Œ 즐겨찾기면 μ§€μ›Œμ•Ό - WebViewPreloader.shared.setFavoriteUrl(url: nil) - favoriteId = nil - UserDefaultsManager.favoriteId = nil - } else { - WebViewPreloader.shared.setFavoriteUrl(url: bookmark.url) - favoriteId = bookmark.id - } - } else { - WebViewPreloader.shared.setFavoriteUrl(url: bookmark.url) - favoriteId = bookmark.id - } - UserDefaultsManager.favoriteId = favoriteId - } - - func bookmark(at indexPath: IndexPath) -> Bookmark { - return boxList[indexPath.section].viewModel(at: indexPath.row).bookmark - } - - func deleteBookmark(at indexPath: IndexPath) { - let bookmarkId = boxList[indexPath.section].viewModel(at: indexPath.row).id - CoreDataManager.shared.deleteBookmark(id: bookmarkId) - boxList[indexPath.section].deleteCell(at: indexPath.row) - output.send(.sendBoxList(boxList: boxList)) - } - - func editBookmark(at indexPath: IndexPath, name: String, url: URL) { - let bookmarkId = boxList[indexPath.section].viewModel(at: indexPath.row).id - CoreDataManager.shared.updateBookmark(id: bookmarkId, name: name, url: url) - boxList[indexPath.section].updateCell(at: indexPath.row, bookmark: Bookmark(id: bookmarkId, name: name, url: url)) - output.send(.reloadRows(idArray: [bookmarkId])) - } - - func reorderBookmark(srcIndexPath: IndexPath, destIndexPath: IndexPath) { - let mover = boxList[srcIndexPath.section].deleteCell(at: srcIndexPath.row) - boxList[destIndexPath.section].insertCell(mover, at: destIndexPath.row) - - let destFolderId = boxList[destIndexPath.section].id - CoreDataManager.shared.moveBookmark(from: srcIndexPath, to: destIndexPath, srcId: mover.id, destFolderId: destFolderId) - } - - func openFolder(_ folderIndex: Int) { - let destFolderId = boxList[folderIndex].id - if boxList[folderIndex].openSectionIfNeeded() { - output.send(.reloadSections(idArray: [destFolderId])) - output.send(.sendBoxList(boxList: boxList)) - } - } - - func addFolder(_ folder: Folder) { - let boxListSectionViewModel = BoxListSectionViewModel(folder: folder) - boxList.append(boxListSectionViewModel) - } - - func deleteFolder(at row: Int) { - sectionsToReload.remove(boxList[row].id) - boxList.remove(at: row) - for box in boxList { - sectionsToReload.update(with: box.id) - } - } - - func editFolderName(at row: Int, name: String) { - boxList[row].name = name - sectionsToReload.update(with: boxList[row].id) - } - - func moveFolder(from: Int, to: Int) { - let mover = boxList.remove(at: from) - boxList.insert(mover, at: to) - for box in boxList { - sectionsToReload.update(with: box.id) - } - } - - func addFolderDirect(_ folder: Folder) { - let boxListSectionViewModel = BoxListSectionViewModel(folder: folder) - boxList.append(boxListSectionViewModel) - output.send(.sendBoxList(boxList: boxList)) - } - - func addBookmarkDirect(_ bookmark: Bookmark, at index: Int) { - boxList[index].boxListCellViewModels.append(BoxListCellViewModel(bookmark: bookmark)) - output.send(.sendBoxList(boxList: boxList)) - } - - func deleteFolderDirect(_ section: Int) { - let folderId = boxList[section].id - CoreDataManager.shared.deleteFolder(id: folderId) - boxList.remove(at: section) - for box in boxList { - sectionsToReload.update(with: box.id) - } - output.send(.sendBoxList(boxList: boxList)) - output.send(.reloadSections(idArray: Array(sectionsToReload))) - sectionsToReload.removeAll() - } - - func editFolderDirect(_ section: Int, name: String) { - let folderId = boxList[section].id - CoreDataManager.shared.updateFolder(id: folderId, name: name) - boxList[section].name = name - sectionsToReload.update(with: boxList[section].id) - output.send(.reloadSections(idArray: Array(sectionsToReload))) - sectionsToReload.removeAll() - } - -} diff --git a/iBox/Sources/BoxList/Edit/EditCell.swift b/iBox/Sources/BoxList/Edit/EditCell.swift deleted file mode 100644 index 9bb7e56..0000000 --- a/iBox/Sources/BoxList/Edit/EditCell.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// EditCell.swift -// iBox -// -// Created by jiyeon on 2/29/24. -// - -import UIKit - -import SnapKit - -class EditCell: UITableViewCell { - - static let reuseIdentifier = "EditCell" - - // MARK: - UI Components - - private let iconView = UIImageView().then { - $0.tintColor = .label - } - - private let titleLabel = UILabel().then { - $0.font = .cellTitleFont - $0.textColor = .label - } - - // MARK: - Initializer - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup Methods - - private func setupProperty() { - selectionStyle = .none - backgroundColor = .clear - } - - private func setupHierarchy() { - addSubview(iconView) - addSubview(titleLabel) - } - - private func setupLayout() { - iconView.snp.makeConstraints { make in - make.width.height.equalTo(20) - make.leading.equalToSuperview().inset(30) - make.centerY.equalToSuperview() - } - - titleLabel.snp.makeConstraints{ make in - make.leading.equalTo(iconView.snp.trailing).offset(20) - make.centerY.equalToSuperview() - } - } - - // MARK: - Bind - - func bind(_ editItem: EditItem) { - iconView.image = UIImage(systemName: editItem.imageString) - titleLabel.text = editItem.title - } - -} diff --git a/iBox/Sources/BoxList/Edit/EditView.swift b/iBox/Sources/BoxList/Edit/EditView.swift deleted file mode 100644 index 5141e4e..0000000 --- a/iBox/Sources/BoxList/Edit/EditView.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// EditView.swift -// iBox -// -// Created by jiyeon on 2/29/24. -// - -import UIKit - -import SnapKit - -class EditView: UIView { - - var delegate: EditViewDelegate? - - private let editItems = [ - EditItem(type: .folder, imageString: "folder.fill", title: "폴더 관리"), - EditItem(type: .bookmark, imageString: "bookmark.fill", title: "뢁마크 관리") - ] - - // MARK: - UI Components - - let tableView = UITableView().then { - $0.backgroundColor = .clear - $0.register(EditCell.self, forCellReuseIdentifier: EditCell.reuseIdentifier) - $0.separatorStyle = .none - } - - // 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 = .backgroundColor - tableView.delegate = self - tableView.dataSource = self - } - - private func setupHierarchy() { - addSubview(tableView) - } - - private func setupLayout() { - tableView.snp.makeConstraints { make in - make.top.equalToSuperview().inset(40) - make.leading.bottom.trailing.equalToSuperview() - } - } - -} - -extension EditView: UITableViewDelegate { - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 55 - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - delegate?.pushViewController(type: editItems[indexPath.row].type) - } - -} - -extension EditView: UITableViewDataSource { - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return editItems.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: EditCell.reuseIdentifier) as? EditCell else { return UITableViewCell() } - cell.bind(editItems[indexPath.row]) - return cell - } - -} diff --git a/iBox/Sources/BoxList/Edit/EditViewController.swift b/iBox/Sources/BoxList/Edit/EditViewController.swift deleted file mode 100644 index e3c8e7b..0000000 --- a/iBox/Sources/BoxList/Edit/EditViewController.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// EditViewController.swift -// iBox -// -// Created by jiyeon on 2/29/24. -// - -import UIKit - -protocol EditViewDelegate { - func pushViewController(type: EditType) -} - -class EditViewController: BottomSheetViewController { - - var delegate: BoxListViewDelegate? - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - sheetView.delegate = self - } - -} - -extension EditViewController: EditViewDelegate { - - func pushViewController(type: EditType) { - delegate?.pushViewController(type: type) - dismiss(animated: false) - } - -} diff --git a/iBox/Sources/BoxList/EditFolder/EditFolderCell.swift b/iBox/Sources/BoxList/EditFolder/EditFolderCell.swift deleted file mode 100644 index 1d5a169..0000000 --- a/iBox/Sources/BoxList/EditFolder/EditFolderCell.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// FolderCell.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 3/11/24. -// - -import UIKit - -class EditFolderCell: UITableViewCell { - static let reuserIdentifier = "folderCell" - - var onDelete: (() -> Void)? - var onEdit: (() -> Void)? - - private let containerView = UIView() - - private let folderView = FolderView() - - private let editButton = UIButton().then{ - $0.configuration = .plain() - $0.configuration?.image = UIImage(systemName: "ellipsis.circle")?.withTintColor(.box, renderingMode: .alwaysOriginal) - - $0.showsMenuAsPrimaryAction = true - } - - private lazy var nameEditAction = UIAction(title: "이름 λ³€κ²½", image: UIImage(systemName: "pencil")) { [weak self] _ in - self?.onEdit?() - } - - private lazy var deleteAction = UIAction(title: "μ‚­μ œ", image: UIImage(systemName: "trash"), attributes: .destructive) { [weak self] _ in - self?.onDelete?() - } - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupProperty() - setupHierarchy() - setupLayout() - configureEditMenu() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func prepareForReuse() { - onEdit = nil - onDelete = nil - } - - private func setupProperty() { - backgroundColor = .tableViewBackgroundColor - selectionStyle = .none - } - - private func setupHierarchy() { - contentView.addSubview(containerView) - containerView.addSubview(folderView) - containerView.addSubview(editButton) - } - - private func setupLayout() { - containerView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - - editButton.snp.makeConstraints { make in - make.top.bottom.equalToSuperview() - make.trailing.equalToSuperview().offset(-10) - make.width.equalTo(30) - } - - folderView.snp.makeConstraints { make in - make.top.bottom.leading.equalToSuperview() - make.trailing.equalTo(editButton.snp.leading) - } - } - - private func configureEditMenu() { - editButton.menu = UIMenu(options: .displayInline, children: [nameEditAction, deleteAction]) - } - - func configure(_ name: String) { - folderView.setFolderName(name) - } - -} diff --git a/iBox/Sources/BoxList/EditFolder/EditFolderView.swift b/iBox/Sources/BoxList/EditFolder/EditFolderView.swift deleted file mode 100644 index 554baa6..0000000 --- a/iBox/Sources/BoxList/EditFolder/EditFolderView.swift +++ /dev/null @@ -1,152 +0,0 @@ -// -// EditFolderView.swift -// iBox -// -// Created by jiyeon on 2/29/24. -// - -import UIKit - -protocol EditFolderViewDelegate: AnyObject { - func deleteFolder(at: IndexPath) - func editFolderName(at: IndexPath, name: String) - func moveFolder(from: Int, to: Int) -} - -class EditFolderView: UIView { - weak var delegate: EditFolderViewDelegate? - - var viewModel: EditFolderViewModel? { - didSet { - viewModel?.delegate = self - tableView.reloadData() - } - } - - private let backgroundView = UIView().then { - $0.clipsToBounds = true - $0.layer.cornerRadius = 20 - $0.backgroundColor = .tableViewBackgroundColor - } - - private let tableView = UITableView().then { - $0.register(EditFolderCell.self, forCellReuseIdentifier: EditFolderCell.reuserIdentifier) - - $0.setEditing(true, animated: true) - $0.backgroundColor = .clear - $0.rowHeight = 50 - $0.separatorInset = .init(top: 0, left: 15, bottom: 0, right: 15) - } - - // MARK: - Initializer - - override init(frame: CGRect) { - super.init(frame: frame) - configureTableView() - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup Methods - - private func configureTableView() { - tableView.dataSource = self - tableView.delegate = self - } - - private func setupProperty() { - backgroundColor = .backgroundColor - } - - private func setupHierarchy() { - addSubview(backgroundView) - backgroundView.addSubview(tableView) - } - - private func setupLayout() { - backgroundView.snp.makeConstraints { make in - make.top.equalTo(safeAreaLayoutGuide).inset(10) - make.leading.trailing.bottom.equalTo(safeAreaLayoutGuide).inset(20) - } - - tableView.snp.makeConstraints { make in - make.top.bottom.equalToSuperview().offset(10) - make.leading.trailing.equalToSuperview() - } - } - -} - -extension EditFolderView: UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - viewModel?.folderCount ?? 0 - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let viewModel else { fatalError() } - guard let cell = tableView.dequeueReusableCell(withIdentifier: EditFolderCell.reuserIdentifier, for: indexPath) as? EditFolderCell else { fatalError() } - cell.onDelete = { [weak self, weak cell] in - guard let self = self, let cell = cell else { return } - if let currentIndexPath = self.tableView.indexPath(for: cell) { - self.delegate?.deleteFolder(at: currentIndexPath) - } - } - cell.onEdit = { [weak self, weak cell] in - guard let self = self, let cell = cell else { return } - if let currentIndexPath = self.tableView.indexPath(for: cell) { - self.editFolderName(at: currentIndexPath) - } - } - cell.configure(viewModel.folderName(at: indexPath)) - return cell - } - - private func editFolderName(at indexPath: IndexPath) { - guard let viewModel else { return } - delegate?.editFolderName(at: indexPath, name: viewModel.folderName(at: indexPath)) - } - -} - -extension EditFolderView: UITableViewDelegate { - - func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { - return .none - } - - func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { - return false - } - - func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { - guard let viewModel else { return } - viewModel.reorderFolder(srcIndexPath: sourceIndexPath, destIndexPath: destinationIndexPath) - delegate?.moveFolder(from: sourceIndexPath.row, to: destinationIndexPath.row) - } -} - -extension EditFolderView: EditFolderViewModelDelegate { - func reloadRow(_ indexPath: IndexPath) { - tableView.reloadRows(at: [indexPath], with: .automatic) - } - - func deleteRow(_ indexPath: IndexPath) { - tableView.deleteRows(at: [indexPath], with: .automatic) - } - - func addRow() { - guard let viewModel else { return } - let indexPath = IndexPath(row: viewModel.folderCount - 1, section: 0) - tableView.insertRows(at: [indexPath], with: .automatic) - } - - func reloadTableView() { - tableView.reloadData() - } -} - diff --git a/iBox/Sources/BoxList/EditFolder/EditFolderViewController.swift b/iBox/Sources/BoxList/EditFolder/EditFolderViewController.swift deleted file mode 100644 index a708a8f..0000000 --- a/iBox/Sources/BoxList/EditFolder/EditFolderViewController.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// EditFolderViewController.swift -// iBox -// -// Created by jiyeon on 2/29/24. -// - -import UIKit - -protocol EditFolderViewControllerDelegate: AnyObject { - func addFolder(_ folder: Folder) - func deleteFolder(at row: Int) - func editFolderName(at row: Int, name: String) - func moveFolder(from: Int, to: Int) -} - -class EditFolderViewController: BaseViewController, BaseViewControllerProtocol { - weak var delegate: EditFolderViewControllerDelegate? - - init(folders: [Folder]) { - super.init(nibName: nil, bundle: nil) - setupEditFolderView(folders) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupNavigationBar() - } - - // MARK: - BaseNavigationBarViewControllerProtocol - - func setupEditFolderView(_ folders: [Folder]) { - guard let contentView = contentView as? EditFolderView else { return } - let viewModel = EditFolderViewModel(folderList: folders) - contentView.viewModel = viewModel - contentView.delegate = self - } - - func setupNavigationBar() { - setNavigationBarTitleLabelText("폴더 관리") - setNavigationBarTitleLabelFont(.semiboldLabelFont) - setNavigationBarAddButtonHidden(false) - setNavigationBarBackButtonHidden(false) - setNavigationBarAddButtonAction(#selector(addButtonTapped)) - } - - @objc private func addButtonTapped() { - let controller = UIAlertController(title: "μƒˆλ‘œμš΄ 폴더", message: "이 ν΄λ”μ˜ 이름을 μž…λ ₯ν•˜μ‹­μ‹œμ˜€.", preferredStyle: .alert) - - let cancelAction = UIAlertAction(title: "μ·¨μ†Œ", style: .default) { _ in return } - let okAction = UIAlertAction(title: "확인", style: .default) { [weak self] action in - guard let name = controller.textFields?.first?.text else { return } - let folder = Folder(id: UUID(), name: name, bookmarks: []) - guard let contentView = self?.contentView as? EditFolderView else { return } - contentView.viewModel?.addFolder(folder) - self?.delegate?.addFolder(folder) - } - controller.addAction(cancelAction) - controller.addAction(okAction) - okAction.isEnabled = false - - controller.addTextField() { textField in - NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: textField, queue: OperationQueue.main, using: - {_ in - let textCount = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines).count ?? 0 - let textIsNotEmpty = textCount > 0 - - okAction.isEnabled = textIsNotEmpty - - }) - } - controller.textFields?.first?.placeholder = "이름" - controller.textFields?.first?.autocorrectionType = .no - controller.textFields?.first?.spellCheckingType = .no - self.present(controller, animated: true) - } - -} - -extension EditFolderViewController: EditFolderViewDelegate { - func moveFolder(from: Int, to: Int) { - delegate?.moveFolder(from: from, to: to) - } - - func deleteFolder(at indexPath: IndexPath) { - recheckDeleteFolder(at: indexPath) - } - - private func recheckDeleteFolder(at indexPath: IndexPath) { - let actionSheetController = UIAlertController(title: nil, message: "λͺ¨λ“  λΆλ§ˆν¬κ°€ μ‚­μ œλ©λ‹ˆλ‹€.", preferredStyle: .alert) - let firstAction = UIAlertAction(title: "폴더 μ‚­μ œ", style: .destructive) {[weak self] _ in - guard let contentView = self?.contentView as? EditFolderView else { return } - contentView.viewModel?.deleteFolder(at: indexPath) - self?.delegate?.deleteFolder(at: indexPath.row) - } - let cancelAction = UIAlertAction(title: "μ·¨μ†Œ", style: .cancel) - actionSheetController.addAction(firstAction) - actionSheetController.addAction(cancelAction) - present(actionSheetController, animated: true) - } - - func editFolderName(at indexPath: IndexPath, name: String) { - let controller = UIAlertController(title: "폴더 이름 λ³€κ²½", message: nil, preferredStyle: .alert) - - let cancelAction = UIAlertAction(title: "μ·¨μ†Œ", style: .default) { _ in return } - let okAction = UIAlertAction(title: "확인", style: .default) { [weak self] action in - guard let newName = controller.textFields?.first?.text else { return } - guard let contentView = self?.contentView as? EditFolderView else { return } - contentView.viewModel?.editFolderName(at: indexPath, name: newName) - self?.delegate?.editFolderName(at: indexPath.row, name: newName) - } - controller.addAction(cancelAction) - controller.addAction(okAction) - okAction.isEnabled = true - - controller.addTextField() { textField in - NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: textField, queue: OperationQueue.main, using: - {_ in - let textCount = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines).count ?? 0 - let textIsNotEmpty = textCount > 0 - - okAction.isEnabled = textIsNotEmpty - - }) - } - controller.textFields?.first?.text = name - controller.textFields?.first?.autocorrectionType = .no - controller.textFields?.first?.spellCheckingType = .no - - self.present(controller, animated: true) - } - -} diff --git a/iBox/Sources/BoxList/EditFolder/EditFolderViewModel.swift b/iBox/Sources/BoxList/EditFolder/EditFolderViewModel.swift deleted file mode 100644 index 5ec7149..0000000 --- a/iBox/Sources/BoxList/EditFolder/EditFolderViewModel.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// EditFolderViewModel.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 3/11/24. -// - -import Combine -import Foundation - -protocol EditFolderViewModelDelegate: AnyObject { - func reloadRow(_ indexPath: IndexPath) - func deleteRow(_ indexPath: IndexPath) - func reloadTableView() - func addRow() -} - -class EditFolderViewModel { - weak var delegate: EditFolderViewModelDelegate? - var folderList: [Folder] - - private let output = PassthroughSubject() - - init(folderList: [Folder]) { - self.folderList = folderList - } - - var folderCount: Int { - folderList.count - } - - func addFolder(_ folder: Folder) { - CoreDataManager.shared.addFolder(folder) - folderList.append(folder) - delegate?.addRow() - } - - func deleteFolder(at indexPath: IndexPath) { - CoreDataManager.shared.deleteFolder(id: folderList[indexPath.row].id) - folderList.remove(at: indexPath.row) - delegate?.deleteRow(indexPath) - } - - func editFolderName(at indexPath: IndexPath, name: String) { - CoreDataManager.shared.updateFolder(id: folderList[indexPath.row].id, name: name) - folderList[indexPath.row].name = name - delegate?.reloadRow(indexPath) - } - - func folderName(at indexPath: IndexPath) -> String { - return folderList[indexPath.row].name - } - - func reorderFolder(srcIndexPath: IndexPath, destIndexPath: IndexPath) { - let mover = folderList.remove(at: srcIndexPath.row) - folderList.insert(mover, at: destIndexPath.row) - CoreDataManager.shared.moveFolder(from: srcIndexPath.row, to: destIndexPath.row) - } -} diff --git a/iBox/Sources/BoxList/FolderButton.swift b/iBox/Sources/BoxList/FolderButton.swift deleted file mode 100644 index 81f548e..0000000 --- a/iBox/Sources/BoxList/FolderButton.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// FolderButton.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/4/24. -// - -import UIKit - -import SnapKit - -class FolderButton: UIButton { - - private var isOpen: Bool = true - - // MARK: - UI Components - - private let folderView = FolderView().then { - $0.isUserInteractionEnabled = false - } - - private let openCloseImageView = UIImageView().then { - $0.tintColor = .tertiaryLabel - $0.contentMode = .scaleAspectFit - } - - // MARK: - Initializer - - init(isOpen: Bool) { - self.isOpen = isOpen - super.init(frame: .zero) - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .tableViewBackgroundColor - let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .medium, scale: .default) - openCloseImageView.image = UIImage(systemName: "chevron.right", withConfiguration: config) - if isOpen { - openCloseImageView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2) - } - } - - private func setupHierarchy() { - addSubview(folderView) - addSubview(openCloseImageView) - } - - private func setupLayout() { - openCloseImageView.snp.makeConstraints { make in - make.centerY.equalToSuperview() - make.width.height.equalTo(15) - make.trailing.equalToSuperview().offset(-20) - } - - folderView.snp.makeConstraints { make in - make.top.bottom.leading.equalToSuperview() - make.trailing.equalTo(openCloseImageView.snp.leading) - } - } - - func setFolderName(_ name: String) { - folderView.setFolderName(name) - } - - func toggleStatus() { - isOpen = !isOpen - if isOpen { - UIView.animate(withDuration: 0.25) { [weak self] in - self?.openCloseImageView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2) - } - } else { - UIView.animate(withDuration: 0.25) { [weak self] in - self?.openCloseImageView.transform = CGAffineTransform(rotationAngle: 0) - } - } - - } -} diff --git a/iBox/Sources/BoxList/FolderView.swift b/iBox/Sources/BoxList/FolderView.swift deleted file mode 100644 index 37af8c4..0000000 --- a/iBox/Sources/BoxList/FolderView.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// FolderView.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 3/11/24. -// - -import UIKit - -class FolderView: UIView { - private let folderImageView = UIImageView().then { - $0.image = UIImage(systemName: "folder.fill") - $0.contentMode = .scaleAspectFit - $0.tintColor = .gray - } - - private let folderNameLabel = UILabel().then { - $0.textColor = .label - $0.font = .boldLabelFont - } - - init() { - super.init(frame: .zero) - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .tableViewBackgroundColor - } - - private func setupHierarchy() { - addSubview(folderImageView) - addSubview(folderNameLabel) - } - - private func setupLayout() { - folderImageView.snp.makeConstraints { make in - make.centerY.equalToSuperview() - make.width.height.equalTo(25) - make.leading.equalToSuperview().offset(20) - } - - folderNameLabel.snp.makeConstraints { make in - make.centerY.equalToSuperview() - make.leading.equalTo(folderImageView.snp.trailing).offset(10) - make.trailing.equalToSuperview().inset(10) - } - } - - func setFolderName(_ name: String) { - folderNameLabel.text = name - } -} - - diff --git a/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenView.swift b/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenView.swift deleted file mode 100644 index d24fcf1..0000000 --- a/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenView.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// CustomLaunchScreenView.swift -// iBox -// -// Created by Chan on 3/2/24. -// - -import UIKit - -import SnapKit - -class CustomLaunchScreenView: UIView { - private let logoImageView = UIImageView() - private var imageViews: [UIImageView] = [] - private let images = ["fox_page0", "fox_page1", "fox_page2", "fox_page3", "fox_page4"] - private var timer: Timer? - - - override init(frame: CGRect) { - super.init(frame: frame) - configureUI() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - configureUI() - } - - private func configureUI() { - backgroundColor = .backgroundColor - logoImageView.image = UIImage(named: "LaunchIcon") - logoImageView.contentMode = .scaleAspectFit - addSubview(logoImageView) - - for imageName in images { - let imageView = UIImageView(image: UIImage(named: imageName)) - imageView.contentMode = .scaleAspectFit - imageView.isHidden = true - imageView.tintColor = .box2 - addSubview(imageView) - imageViews.append(imageView) - } - - logoImageView.snp.makeConstraints { make in - make.center.equalToSuperview() - make.width.height.equalTo(200) - } - - imageViews.forEach { imageView in - imageView.snp.makeConstraints { make in - make.bottom.equalToSuperview().inset(20) - make.left.equalToSuperview().inset(20) - make.width.height.equalTo(32) - } - } - - changeImages() - } - - private func changeImages() { - var currentIndex = 0 - - timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] timer in - guard let self = self else { return } - - let state = AppStateManager.shared.versionCheckCompleted - if state == .success || state == .later || state == .maxRetryReached { - timer.invalidate() - self.timer = nil - return - } - - self.imageViews.forEach { $0.isHidden = true } - self.imageViews[currentIndex].isHidden = false - - currentIndex = (currentIndex + 1) % self.imageViews.count - } - } -} diff --git a/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenViewController.swift b/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenViewController.swift deleted file mode 100644 index 1c63d5b..0000000 --- a/iBox/Sources/CustomLaunchScreen/CustomLaunchScreenViewController.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// CustomLaunchScreenViewController.swift -// iBox -// -// Created by Chan on 3/2/24. -// - -import UIKit -import Combine - -class CustomLaunchScreenViewController: UIViewController { - private var customLaunchScreenView: CustomLaunchScreenView! - private var cancellables: Set = [] - private var urlContext: UIOpenURLContext? - - init(urlContext: UIOpenURLContext?) { - self.urlContext = urlContext - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - configureLaunchScreenView() - observeVersionCheckCompletion() - } - - private func configureLaunchScreenView() { - customLaunchScreenView = CustomLaunchScreenView(frame: self.view.bounds) - self.view.addSubview(customLaunchScreenView) - - customLaunchScreenView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - } - - // MARK: - Custom Update Checker View (μ˜ˆμ •) - private func observeVersionCheckCompletion() { - AppStateManager.shared.$versionCheckCompleted - .receive(on: DispatchQueue.main) - .sink { [weak self] result in - switch result { - case .success, .maxRetryReached, .later: - self?.transitionToNextScreen() - print("App 정상 μ‹€ν–‰") - case .urlError: - print("URL μ—λŸ¬κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.") - case .networkError: - print("λ„€νŠΈμ›Œν¬ μš”μ²­μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.") - case .decodingError: - print("응닡 디코딩에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.") - case .versionOutdated(let mandatoryUpdate, let updateUrl): - if mandatoryUpdate { - print("ν•„μˆ˜ μ—…λ°μ΄νŠΈκ°€ ν•„μš”ν•©λ‹ˆλ‹€. μ—…λ°μ΄νŠΈ URL: \(updateUrl)") - } else { - print("μ—…λ°μ΄νŠΈκ°€ μžˆμŠ΅λ‹ˆλ‹€. μ—…λ°μ΄νŠΈ URL: \(updateUrl)") - } - case .serverError: - print("μ„œλ²„ μ—λŸ¬ λ˜λŠ” 기타 μ—λŸ¬κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.") - case .update: - print("μ—…λ°μ΄νŠΈ 클릭") - case .internalSceneError: - print("scene error μˆ˜μ§‘") - case .internalInfoError: - print("info error μˆ˜μ§‘") - case .initial: - self?.startupFlow() - print("init") - } - } - .store(in: &cancellables) - } - - private func startupFlow() { - DefaultData.insertDefaultDataIfNeeded() - } - - private func transitionToNextScreen() { - guard let window = self.view.window else { return } - - let mainViewController = MainTabBarController() - window.rootViewController = mainViewController - - if let urlContext = self.urlContext, - let tabBarController = window.rootViewController as? UITabBarController { - 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/Error/ErrorCode.swift b/iBox/Sources/Error/ErrorCode.swift deleted file mode 100644 index 65a90fe..0000000 --- a/iBox/Sources/Error/ErrorCode.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// ErrorCode.swift -// iBox -// -// Created by Chan on 4/23/24. -// - -import Foundation - -enum ViewErrorCode: Equatable { - case normal - case unknown - case webContentProcessTerminated - case webViewInvalidated - case javaScriptExceptionOccurred - case javaScriptResultTypeIsUnsupported - case networkError(URLError) -} diff --git a/iBox/Sources/Error/ErrorPageView.swift b/iBox/Sources/Error/ErrorPageView.swift deleted file mode 100644 index 8c3836b..0000000 --- a/iBox/Sources/Error/ErrorPageView.swift +++ /dev/null @@ -1,187 +0,0 @@ -// -// ErrorPageView.swift -// iBox -// -// Created by Chan on 4/18/24. -// - -import UIKit - -import SnapKit - -class ErrorPageView: UIView { - private var imageViews: [UIImageView] = [] - private let images = ["fox_page0", "fox_page1", "fox_page2", "fox_page3", "fox_page4"] - var timer: Timer? - - private let backPannelView = UIView().then { - $0.backgroundColor = .backgroundColor - $0.clipsToBounds = true - $0.layer.cornerRadius = 15 - } - - private let problemUrlLabel = UILabel().then { - $0.font = .cellTitleFont - $0.textAlignment = .center - $0.numberOfLines = 0 - $0.lineBreakMode = .byTruncatingTail - } - - private let messageLabel = UILabel().then { - $0.textAlignment = .center - $0.numberOfLines = 0 - $0.text = "ν•΄λ‹Ή μ£Όμ†Œμ—μ„œ λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€." - $0.font = .boldLabelFont - } - - let closeButton = UIButton().then { - $0.configuration = .plain() - $0.configuration?.attributedTitle = .init( - "λ‹«κΈ°", - attributes: .init([.font: UIFont.boldLabelFont, .foregroundColor: UIColor.white]) - ) - $0.setTitleColor(.white, for: .normal) - $0.backgroundColor = .systemGray - $0.layer.cornerRadius = 10 - $0.addAnimationForStateChange(from: .box, to: .systemGray) - } - - let retryButton = UIButton().then { - $0.configuration = .plain() - $0.configuration?.attributedTitle = .init( - "μƒˆλ‘œκ³ μΉ¨", - attributes: .init([.font: UIFont.boldLabelFont, .foregroundColor: UIColor.white]) - ) - $0.setTitleColor(.white, for: .normal) - $0.backgroundColor = .systemGray - $0.layer.cornerRadius = 10 - $0.addAnimationForStateChange(from: .box, to: .systemGray) - } - - let backButton = UIButton().then { - $0.configuration = .plain() - $0.configuration?.attributedTitle = .init( - "λ‚˜κ°€κΈ°", - attributes: .init([.font: UIFont.boldLabelFont, .foregroundColor: UIColor.white]) - ) - $0.backgroundColor = .box2 - $0.layer.cornerRadius = 10 - $0.addAnimationForStateChange(from: .box, to: .box2) - } - - let stackView = UIStackView().then { - $0.axis = .horizontal - $0.spacing = 20 - } - - override init(frame: CGRect) { - super.init(frame: frame) - setupProperty() - setupHierarchy() - setupAnimation() - setupLayout() - changeImages() - } - - override func willMove(toSuperview newSuperview: UIView?) { - super.willMove(toSuperview: newSuperview) - - if newSuperview == nil { - timer?.invalidate() - timer = nil - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupProperty() { - changeImages() - } - - private func setupHierarchy() { - addSubview(backPannelView) - backPannelView.addSubview(messageLabel) - backPannelView.addSubview(problemUrlLabel) - backPannelView.addSubview(stackView) - stackView.addArrangedSubview(backButton) - stackView.addArrangedSubview(retryButton) - stackView.addArrangedSubview(closeButton) - } - - private func setupAnimation() { - for imageName in images { - let imageView = UIImageView(image: UIImage(named: imageName)) - imageView.contentMode = .scaleAspectFit - imageView.isHidden = true - imageView.tintColor = .box2 - addSubview(imageView) - imageViews.append(imageView) - } - } - - private func setupLayout() { - imageViews.forEach { imageView in - imageView.snp.makeConstraints { make in - make.centerX.equalToSuperview() - make.bottom.equalTo(backPannelView.snp.top).offset(5) - make.width.height.equalTo(38) - } - } - - backPannelView.snp.makeConstraints { make in - make.top.equalToSuperview().inset(100) - make.trailing.leading.equalToSuperview().inset(20) - make.height.equalToSuperview().multipliedBy(0.4) - } - - problemUrlLabel.snp.makeConstraints { make in - make.top.equalToSuperview().inset(20) - make.bottom.equalTo(messageLabel.snp.top).offset(-20) - make.leading.trailing.equalToSuperview().inset(20) - } - - messageLabel.snp.makeConstraints { make in - make.bottom.equalTo(stackView.snp.top).offset(-20) - make.leading.trailing.equalToSuperview().inset(20) - } - - stackView.snp.makeConstraints { make in - make.leading.bottom.trailing.equalToSuperview().inset(20) - } - - [backButton, retryButton, closeButton].forEach { button in - button.snp.makeConstraints { make in - make.height.equalTo(40) - } - } - } - - func configure(with error: Error, url: String) { - problemUrlLabel.text = "\(url)" - print(error.localizedDescription) - } - - private func changeImages() { - var currentIndex = 0 - - timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] timer in - guard let self = self else { - timer.invalidate() - return - } - - let state = AppStateManager.shared.currentViewErrorState - - if state == .normal { - return - } - - self.imageViews.forEach { $0.isHidden = true } - self.imageViews[currentIndex].isHidden = false - - currentIndex = (currentIndex + 1) % self.imageViews.count - } - } -} diff --git a/iBox/Sources/Error/ErrorPageViewController.swift b/iBox/Sources/Error/ErrorPageViewController.swift deleted file mode 100644 index 0cc570e..0000000 --- a/iBox/Sources/Error/ErrorPageViewController.swift +++ /dev/null @@ -1,154 +0,0 @@ -// -// ErrorPageViewController.swift -// iBox -// -// Created by Chan on 4/18/24. -// - -import UIKit -import WebKit - -protocol ErrorPageControllerDelegate: AnyObject { - func presentErrorPage(_ errorPage: ErrorPageViewController) - func backButton() -} - -protocol WebViewErrorDelegate { - func webView(_ webView: WebView, didFailWithError error: Error, url: URL?) -} - -class ErrorPageViewController: UIViewController { - weak var delegate: ErrorPageControllerDelegate? - var webView: WebView? - var isHandlingError = false - - let slideInPresentationManager = SlideInPresentationManager() - - init(webView: WebView) { - super.init(nibName: nil, bundle: nil) - self.webView = webView - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func loadView() { - self.view = ErrorPageView() - } - - override func viewDidLoad() { - super.viewDidLoad() - setupProperty() - setupPresentation() - } - - override func viewWillDisappear(_ animated: Bool) { - resetErrorHandling() - } - - private func setupProperty() { - if let errorPageView = view as? ErrorPageView { - errorPageView.retryButton.addTarget(self, action: #selector(retryButtonTapped), for: .touchUpInside) - errorPageView.closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside) - errorPageView.backButton.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside) - } - } - - func setupPresentation() { - self.modalPresentationStyle = .custom - self.transitioningDelegate = slideInPresentationManager - } - - func configureWithError(_ error: Error, url: String) { - if let errorPageView = view as? ErrorPageView { - errorPageView.configure(with: error, url: url) - } - } - - @objc private func retryButtonTapped() { - webView?.retryLoading() - } - - @objc private func closeButtonTapped() { - dismiss(animated: true) - } - - @objc func backButtonTapped() { - self.dismiss(animated: true) { - self.delegate?.backButton() - } - } - - func handleError(_ error: Error, _ url: URL?) { - guard !isHandlingError else { return } - isHandlingError = true - - if presentedViewController != nil { - dismiss(animated: true) { - self.configureWithError(error, url: url?.absoluteString ?? "") - self.delegate?.presentErrorPage(self) - } - } else { - configureWithError(error, url: url?.absoluteString ?? "") - delegate?.presentErrorPage(self) - } - } - - func resetErrorHandling() { - isHandlingError = false - } - - private func convertErrorToViewErrorCode(_ error: Error) -> ViewErrorCode { - if let wkError = error as? WKError { - switch wkError.code { - case .webContentProcessTerminated: - return .webContentProcessTerminated - case .webViewInvalidated: - return .webViewInvalidated - case .javaScriptExceptionOccurred: - return .javaScriptExceptionOccurred - case .javaScriptResultTypeIsUnsupported: - return .javaScriptResultTypeIsUnsupported - default: - return .unknown - } - } else if let urlError = error as? URLError { - return .networkError(urlError) - } - - return .unknown - } - - private func handleViewError(_ error: ViewErrorCode) { - switch error { - case .normal: - print("OK.") - case .unknown: - print("Unknown error occurred in the view.") - case .webContentProcessTerminated: - print("Web content process has been terminated unexpectedly.") - case .webViewInvalidated: - print("The web view has been invalidated.") - case .javaScriptExceptionOccurred: - print("A JavaScript exception occurred.") - case .javaScriptResultTypeIsUnsupported: - print("JavaScript returned a result type that is not supported.") - case .networkError(let urlError): - print("Network error occurred: \(urlError.localizedDescription)") - } - - AppStateManager.shared.updateViewError(error) - } -} - -extension ErrorPageViewController: WebViewErrorDelegate { - - func webView(_ webView: WebView, didFailWithError error: Error, url: URL?) { - handleError(error, url) - - let viewErrorCode = convertErrorToViewErrorCode(error) - handleViewError(viewErrorCode) - } - -} diff --git a/iBox/Sources/Extension/Notification+Extension.swift b/iBox/Sources/Extension/Notification+Extension.swift deleted file mode 100644 index fbe5b65..0000000 --- a/iBox/Sources/Extension/Notification+Extension.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Notification+Extension.swift -// iBox -// -// Created by jiyeon on 4/11/24. -// - -import Foundation - -extension Notification.Name { - - static let didResetData = Notification.Name("didResetData") - static let didAddBookmark = Notification.Name("didAddBookmark") - -} diff --git a/iBox/Sources/Extension/UIButton+Extension.swift b/iBox/Sources/Extension/UIButton+Extension.swift deleted file mode 100644 index 16dee87..0000000 --- a/iBox/Sources/Extension/UIButton+Extension.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// UIButton+Extension.swift -// iBox -// -// Created by Chan on 4/25/24. -// - -import UIKit -import ObjectiveC - -private var touchDownColorKey: UInt8 = 0 -private var touchUpColorKey: UInt8 = 0 - -extension UIButton { - var touchDownColor: UIColor? { - get { - return objc_getAssociatedObject(self, &touchDownColorKey) as? UIColor - } - set { - objc_setAssociatedObject(self, &touchDownColorKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } - - var touchUpColor: UIColor? { - get { - return objc_getAssociatedObject(self, &touchUpColorKey) as? UIColor - } - set { - objc_setAssociatedObject(self, &touchUpColorKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } - - func addAnimationForStateChange(from: UIColor, to: UIColor) { - self.touchDownColor = from - self.touchUpColor = to - addTarget(self, action: #selector(animateOnTouchDown), for: .touchDown) - addTarget(self, action: #selector(animateOnTouchUp), for: [.touchUpInside, .touchUpOutside, .touchCancel]) - } - - @objc private func animateOnTouchDown() { - if let color = touchDownColor { - UIView.animate(withDuration: 0.3) { - self.backgroundColor = color - } - } - } - - @objc private func animateOnTouchUp() { - if let color = touchUpColor { - UIView.animate(withDuration: 0.3) { - self.backgroundColor = color - } - } - } -} diff --git a/iBox/Sources/Extension/UIColor+Extension.swift b/iBox/Sources/Extension/UIColor+Extension.swift deleted file mode 100644 index 2a5fc3f..0000000 --- a/iBox/Sources/Extension/UIColor+Extension.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// UIColor+Extension.swift -// iBox -// -// Created by jiyeon on 1/3/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) - ) - } - - private static func color(light: UIColor, dark: UIColor) -> UIColor { - return UIColor { traitCollection in - switch traitCollection.userInterfaceStyle { - case .dark: - return dark - default: - return light - } - } - } - - static let box = UIColor(hex: 0xFF7F29) - static let box2 = UIColor(hex: 0xFF9548) - static let box3 = UIColor(hex: 0xFFDC6E) - static let boxWithOpacity = UIColor(hex: 0xFF7F29, alpha: 0.7) - static let box2WithOpacity = UIColor(hex: 0xFF9548, alpha: 0.7) - static let tableViewBackgroundColor = color(light: .systemGroupedBackground, dark: .systemGray4) - static let folderGray = color(light: .systemGray3, dark: .systemGray2) - static let webIconColor = color(light: .black, dark: .systemGray) - static let dimmedViewColor = UIColor.black.withAlphaComponent(0.75) - static let backgroundColor = color(light: .white, dark: UIColor(hex: 0x242424)) - static let invertBackgroundColor = color(light: UIColor(hex: 0x242424), dark: .white) -} diff --git a/iBox/Sources/Extension/UIFont+Extension.swift b/iBox/Sources/Extension/UIFont+Extension.swift deleted file mode 100644 index cdca19a..0000000 --- a/iBox/Sources/Extension/UIFont+Extension.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// UIFont+Extension.swift -// iBox -// -// Created by jiyeon on 3/26/24. -// - -import UIKit - -extension UIFont { - - static let titleFont = UIFont.systemFont(ofSize: 20.0, weight: .bold) - static let subTitlefont = UIFont.systemFont(ofSize: 16.0, weight: .semibold) - static let barItemFont = UIFont.systemFont(ofSize: 14.5, weight: .semibold) - static let labelFont = UIFont.systemFont(ofSize: 16.0) - static let semiboldLabelFont = UIFont.systemFont(ofSize: 16.0, weight: .semibold) - static let boldLabelFont = UIFont.systemFont(ofSize: 14.5, weight: .bold) - static let cellTitleFont = UIFont.systemFont(ofSize: 14.5) - static let cellDescriptionFont = UIFont.systemFont(ofSize: 12.0, weight: .regular) - static let descriptionFont = UIFont.systemFont(ofSize: 12.5) - static let refreshControlFont = UIFont.boldSystemFont(ofSize: 11.0) - static let emptyLabelFont = UIFont.systemFont(ofSize: 15.0, weight: .regular) -} diff --git a/iBox/Sources/Extension/UIImage+Extension.swift b/iBox/Sources/Extension/UIImage+Extension.swift deleted file mode 100644 index 1b7e640..0000000 --- a/iBox/Sources/Extension/UIImage+Extension.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// UIImage+Extension.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 4/11/24. -// - -import UIKit - -extension UIImage { - func imageWithColor(_ color: UIColor) -> UIImage? { - UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale); - guard let context = UIGraphicsGetCurrentContext(), let cgImage = self.cgImage else { return nil } - - context.translateBy(x: 0, y: self.size.height) - context.scaleBy(x: 1.0, y: -1.0); - context.setBlendMode(.normal) - let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) - context.clip(to: rect, mask: cgImage) - color.setFill() - context.fill(rect) - let newImage = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext(); - - return newImage - } -} diff --git a/iBox/Sources/Extension/UIView+Extension.swift b/iBox/Sources/Extension/UIView+Extension.swift deleted file mode 100644 index e7f584c..0000000 --- a/iBox/Sources/Extension/UIView+Extension.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// UIView+Extension.swift -// iBox -// -// Created by jiyeon on 1/3/24. -// - -import UIKit - -protocol Then {} - -extension Then where Self: AnyObject { - - func then(block: (Self) -> Void) -> Self { - block(self) - return self - } - -} - -extension UIView: Then {} - -extension UIView { - - func toUserInterfaceStyle(_ theme: Theme) -> UIUserInterfaceStyle { - switch theme { - case .light: return UIUserInterfaceStyle.light - case .dark: return UIUserInterfaceStyle.dark - case .system: return UIUserInterfaceStyle.unspecified - } - } - - func roundCorners(_ corners: UIRectCorner, radius: CGFloat) { - DispatchQueue.main.async { // ν™•μ‹€νžˆ 메인 μŠ€λ ˆλ“œμ—μ„œ μ‹€ν–‰λ˜λ„λ‘ κ°•μ œ - let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) - let mask = CAShapeLayer() - mask.path = path.cgPath - mask.frame = self.bounds - self.layer.mask = mask - } - } - - // MARK: - λ·° 계측 ꡬ쑰 log - func printViewHierarchy(level: Int = 0) { - let padding = String(repeating: " ", count: level * 2) - let viewInfo = "\(padding)\(type(of: self)) - Frame: \(self.frame)" - print(viewInfo) - - for subview in self.subviews { - subview.printViewHierarchy(level: level + 1) - } - } - -} diff --git a/iBox/Sources/Extension/UIViewController+Extension.swift b/iBox/Sources/Extension/UIViewController+Extension.swift deleted file mode 100644 index 71a9797..0000000 --- a/iBox/Sources/Extension/UIViewController+Extension.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// 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 findAddBookmarkViewController() -> AddBookmarkViewController? { - if let navigationController = presentedViewController as? UINavigationController, - let vc = navigationController.topViewController as? AddBookmarkViewController { - return vc - } - return nil - } - -} diff --git a/iBox/Sources/Favorite/FavoriteView.swift b/iBox/Sources/Favorite/FavoriteView.swift deleted file mode 100644 index fd475f3..0000000 --- a/iBox/Sources/Favorite/FavoriteView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// FavoriteView.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/18/24. -// - -import UIKit -import WebKit - -import SnapKit - -class FavoriteView: UIView { - - var webView: WebView? - - // MARK: - Initializer - - override init(frame: CGRect) { - super.init(frame: frame) - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layoutSubviews() { - super.layoutSubviews() - webView?.setupRefreshControl() - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .backgroundColor - - loadFavoriteWeb() - webView = WebViewPreloader.shared.getFavoriteView() - } - - private func setupHierarchy() { - guard let webView else { return } - addSubview(webView) - } - - private func setupLayout() { - guard let webView else { return } - webView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - } - - private func loadFavoriteWeb() { - let favoriteId = UserDefaultsManager.favoriteId - var favoriteUrl: URL? = nil - if let favoriteId { - favoriteUrl = CoreDataManager.shared.getBookmarkUrl(favoriteId) - if favoriteUrl == nil { - UserDefaultsManager.favoriteId = nil - } - } - WebViewPreloader.shared.preloadFavoriteView(url: favoriteUrl) - } - -} diff --git a/iBox/Sources/Favorite/FavoriteViewController.swift b/iBox/Sources/Favorite/FavoriteViewController.swift deleted file mode 100644 index 237813c..0000000 --- a/iBox/Sources/Favorite/FavoriteViewController.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// FavoriteViewController.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 12/27/23. -// - -import UIKit - -class FavoriteViewController: BaseViewController, BaseViewControllerProtocol { - - var delegate: AddBookmarkViewControllerProtocol? - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupNavigationBar() - - guard let contentView = contentView as? FavoriteView else { return } - contentView.webView?.delegate = self - } - - // MARK: - BaseViewControllerProtocol - - func setupNavigationBar() { - setNavigationBarHidden(true) - } - -} - -extension FavoriteViewController: WebViewDelegate { - - func pushAddBookMarkViewController(url: URL) { - let encodingURL = url.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" - - if let iBoxUrl = URL(string: "iBox://url?data=" + encodingURL) { - if let tabBarController = findMainTabBarController() { - AddBookmarkManager.shared.navigateToAddBookmarkView(from: iBoxUrl, in: tabBarController) - } - } - } - -} diff --git a/iBox/Sources/Initializer/DefaultData.swift b/iBox/Sources/Initializer/DefaultData.swift deleted file mode 100644 index 4cc8522..0000000 --- a/iBox/Sources/Initializer/DefaultData.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// DefaultData.swift -// iBox -// -// Created by Chan on 4/17/24. -// - -import Foundation - - -class DefaultData { - - static func insertDefaultDataIfNeeded(_ isReset: Bool = false, completion: (() -> Void)? = nil) { - let isDefaultDataInserted = UserDefaultsManager.isDefaultDataInserted - if !isDefaultDataInserted || isReset { - fetchDefaultData { defaultFolders in - DispatchQueue.main.async { - CoreDataManager.shared.deleteAllFolders() - CoreDataManager.shared.addInitialFolders(defaultFolders) - UserDefaultsManager.isDefaultDataInserted = true - completion?() - } - } - } - } - - static func fetchDefaultData(completion: @escaping ([Folder]) -> Void) { - let localDic: [String : String] = ["Seoul" : "default-kr", "default" : "default"] - let cityName = "Seoul" // μΆ”ν›„ global μ˜ˆμ • - let local = localDic[cityName] ?? "default" - - let url = URL(string: "https://raw.githubusercontent.com/42Box/versioning/main/\(local).json")! - URLSession.shared.dataTask(with: url) { data, response, error in - guard let data = data, error == nil else { - print("Error fetching default data: \(String(describing: error))") - completion(DefaultDataLoader.defaultData) - return - } - - do { - let folderData = try JSONDecoder().decode(FolderData.self, from: data) - let folders = [Folder(id: UUID(), name: "42 \(cityName)", bookmarks: folderData.list.map { Bookmark(id: UUID(), name: $0.name, url: URL(string: $0.url)!) })] - completion(folders) - } catch { - print("Error decoding JSON: \(error)") - completion(DefaultDataLoader.defaultData) - } - }.resume() - } -} diff --git a/iBox/Sources/Initializer/DefaultDataModel.swift b/iBox/Sources/Initializer/DefaultDataModel.swift deleted file mode 100644 index 7a3bb89..0000000 --- a/iBox/Sources/Initializer/DefaultDataModel.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// DefaultDataModel.swift -// iBox -// -// Created by Chan on 4/17/24. -// - -import Foundation - - -struct FolderData: Codable { - var list: [BookmarkData] -} - -struct BookmarkData: Codable { - var name: String - var url: String -} - -struct DefaultDataLoader { - static let defaultData = [ - Folder(id: UUID(), name: "42 폴더", bookmarks: [ - Bookmark(id: UUID(), name: "42 Intra", url: URL(string: "https://profile.intra.42.fr/")!), - Bookmark(id: UUID(), name: "42Where", url: URL(string: "https://www.where42.kr/")! ), - Bookmark(id: UUID(), name: "42Stat", url: URL(string: "https://stat.42seoul.kr/")!), - Bookmark(id: UUID(), name: "μ§‘ν˜„μ „", url: URL(string: "https://42library.kr/")!), - Bookmark(id: UUID(), name: "42gg", url: URL(string: "https://gg.42seoul.kr/")!), - Bookmark(id: UUID(), name: "24HANE", url: URL(string: "https://24hoursarenotenough.42seoul.kr/")!) - ]) - ] -} diff --git a/iBox/Sources/Main/MainTabBarController.swift b/iBox/Sources/Main/MainTabBarController.swift deleted file mode 100644 index f1b8329..0000000 --- a/iBox/Sources/Main/MainTabBarController.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// MainTabBarController.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 12/27/23. -// - -import UIKit - -class MainTabBarController: UITabBarController { - - var previousTabIndex = 0 - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - delegate = self - view.backgroundColor = .backgroundColor - - setupTabBar() - setupTabBarAppearance() - } - - // MARK: - Setup Methods - - private func setupTabBar() { - viewControllers = [ - setupViewController(viewController: BoxListViewController(), image: UIImage(systemName: "square.grid.2x2.fill")), - setupViewController(viewController: FavoriteViewController(), image: UIImage(systemName: "heart.fill")), - setupViewController(viewController: SettingsViewController(), image: UIImage(systemName: "gearshape.fill")) - ] - tabBar.tintColor = .box - tabBar.backgroundColor = .backgroundColor - selectedIndex = UserDefaultsManager.homeTabIndex - } - - private func setupViewController(viewController: UIViewController, image: UIImage?) -> UIViewController { - viewController.tabBarItem.title = "" - viewController.tabBarItem.image = image - return UINavigationController(rootViewController: viewController) - } - - private func setupTabBarAppearance() { - let appearance = UITabBarItem.appearance() - appearance.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], for: .normal) - appearance.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], for: .selected) - } - -} - -extension MainTabBarController: UITabBarControllerDelegate { - - func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .light) - generator.prepare() - generator.impactOccurred() - } - if tabBarController.selectedIndex == 1 && previousTabIndex == 1 { - WebViewPreloader.shared.resetFavoriteView() - } - previousTabIndex = tabBarController.selectedIndex - } - -} diff --git a/iBox/Sources/MainView.swift b/iBox/Sources/MainView.swift new file mode 100644 index 0000000..9d37b15 --- /dev/null +++ b/iBox/Sources/MainView.swift @@ -0,0 +1,46 @@ +// +// MainView.swift +// iBox +// +// Created by jiyeon on 12/26/23. +// + +import UIKit + +import SnapKit + +class MainView: UIView { + + // MARK: - UI + + var label: UILabel = { + let label = UILabel() + label.text = "μ˜ˆμ‹œμž…λ‹ˆλ‹Ή" + label.textColor = .black + return label + }() + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: frame) + configureUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - configure UI + + func configureUI() { + backgroundColor = .systemBackground + + addSubview(label) + + label.snp.makeConstraints { + $0.center.equalToSuperview() + } + } + +} diff --git a/iBox/Sources/MainViewController.swift b/iBox/Sources/MainViewController.swift new file mode 100644 index 0000000..ee81407 --- /dev/null +++ b/iBox/Sources/MainViewController.swift @@ -0,0 +1,18 @@ +// +// MainViewController.swift +// iBox +// +// Created by jiyeon on 12/26/23. +// + +import UIKit + +class MainViewController: BaseViewController { + + // MARK: - life cycle + + override func viewDidLoad() { + super.viewDidLoad() + } + +} diff --git a/iBox/Sources/Model/Bookmark.swift b/iBox/Sources/Model/Bookmark.swift deleted file mode 100644 index 260beb0..0000000 --- a/iBox/Sources/Model/Bookmark.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Bookmark.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/30/24. -// - -import Foundation - -struct Bookmark: Codable { - let id: UUID - var name: String - var url: URL -} diff --git a/iBox/Sources/Model/BookmarkError.swift b/iBox/Sources/Model/BookmarkError.swift deleted file mode 100644 index 0128173..0000000 --- a/iBox/Sources/Model/BookmarkError.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// BookmarkError.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 4/21/24. -// - -import Foundation - -enum BookmarkError { - case htmlError - case decodeError - case parseError -} diff --git a/iBox/Sources/Model/EditItem.swift b/iBox/Sources/Model/EditItem.swift deleted file mode 100644 index 83f5689..0000000 --- a/iBox/Sources/Model/EditItem.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// EditItem.swift -// iBox -// -// Created by jiyeon on 2/29/24. -// - -import Foundation - -enum EditType { - case folder - case bookmark -} - -struct EditItem { - var type: EditType - var imageString: String - var title: String -} diff --git a/iBox/Sources/Model/Folder.swift b/iBox/Sources/Model/Folder.swift deleted file mode 100644 index b13d1b8..0000000 --- a/iBox/Sources/Model/Folder.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Folder.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/4/24. -// - -import Foundation - -struct Folder: Codable { - let id: UUID - var name: String - var bookmarks: [Bookmark] -} - diff --git a/iBox/Sources/Model/HomeTabType.swift b/iBox/Sources/Model/HomeTabType.swift deleted file mode 100644 index 2d7bb4c..0000000 --- a/iBox/Sources/Model/HomeTabType.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// HomeTabType.swift -// iBox -// -// Created by jiyeon on 2/22/24. -// - -import Foundation - -enum HomeTabType: CaseIterable { - case boxList - case favorite - - func toString() -> String { - switch self { - case .boxList: "뢁마크 λͺ©λ‘" - case .favorite: "즐겨찾기" - } - } -} diff --git a/iBox/Sources/Model/Metadata.swift b/iBox/Sources/Model/Metadata.swift deleted file mode 100644 index 8f95deb..0000000 --- a/iBox/Sources/Model/Metadata.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Metadata.swift -// iBoxShareExtension -// -// Created by 김찬희 on 2024/03/14. -// - -struct Metadata { - var title: String? - var faviconUrl: String? - var url: String? -} diff --git a/iBox/Sources/Model/SettingsItem.swift b/iBox/Sources/Model/SettingsItem.swift deleted file mode 100644 index 36f3981..0000000 --- a/iBox/Sources/Model/SettingsItem.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// SettingsItem.swift -// iBox -// -// Created by jiyeon on 1/3/24. -// - -import Foundation - -enum SettingsType { - case theme - case homeTab - case haptics - case reset - case guide - - func toString() -> String { - switch self { - case .theme: "ν…Œλ§ˆ" - case .homeTab: "μ‹œμž‘ ν™”λ©΄" - case .haptics: "진동" - case .reset: "데이터 μ΄ˆκΈ°ν™”" - case .guide: "μ•± μ†Œκ°œ" - } - } - -} - -struct SettingsItem { - var type: SettingsType - var description: String? - var flag: Bool? -} diff --git a/iBox/Sources/Model/Theme.swift b/iBox/Sources/Model/Theme.swift deleted file mode 100644 index aceb433..0000000 --- a/iBox/Sources/Model/Theme.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Theme.swift -// iBox -// -// Created by jiyeon on 1/4/24. -// - -import UIKit - -enum Theme: Codable, CaseIterable { - case light - case dark - case system - - func toString() -> String { - switch self { - case .light: "라이트 λͺ¨λ“œ" - case .dark: "닀크 λͺ¨λ“œ" - case .system: "μ‹œμŠ€ν…œ μ„€μ • λͺ¨λ“œ" - } - } - - func toImageString() -> String { - switch self { - case .light: "circle" - case .dark: "circle.fill" - case .system: "circle.righthalf.filled" - } - } -} diff --git a/iBox/Sources/Model/VersionInfo.swift b/iBox/Sources/Model/VersionInfo.swift deleted file mode 100644 index 1f83182..0000000 --- a/iBox/Sources/Model/VersionInfo.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Versioning.swift -// iBox -// -// Created by Chan on 2/29/24. -// - -// MARK: - VersionInfo -struct VersionInfo: Codable { - let version: [Version] - let url: URLClass -} - -// MARK: - URLClass -struct URLClass: Codable { - let storeUrl: String -} - -// MARK: - Version -struct Version: Codable { - let id: Int - let latestVersion, minRequiredVersion: String -} diff --git a/iBox/Sources/SceneDelegate.swift b/iBox/Sources/SceneDelegate.swift index 6672167..f820efc 100644 --- a/iBox/Sources/SceneDelegate.swift +++ b/iBox/Sources/SceneDelegate.swift @@ -10,26 +10,19 @@ import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(frame: windowScene.coordinateSpace.bounds) window?.windowScene = windowScene - - window?.overrideUserInterfaceStyle = window?.toUserInterfaceStyle(UserDefaultsManager.theme) ?? .unspecified - window?.rootViewController = CustomLaunchScreenViewController(urlContext: connectionOptions.urlContexts.first) - window?.makeKeyAndVisible() + let mainViewController = MainViewController() - } - - func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - if let urlContext = URLContexts.first, - let tabBarController = window?.rootViewController as? UITabBarController { - AddBookmarkManager.shared.navigateToAddBookmarkView(from: urlContext.url, in: tabBarController) - } + let navigationController = UINavigationController(rootViewController: mainViewController) + + window?.rootViewController = navigationController // 루트 뷰컨트둀러 μ„€μ • + window?.makeKeyAndVisible() // μœˆλ„μš°λ₯Ό 화면에 λ³΄μ—¬μ€Œ } func sceneDidDisconnect(_ scene: UIScene) { @@ -60,7 +53,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // to restore the scene back to its current state. // Save changes in the application's managed object context when the application transitions to the background. + (UIApplication.shared.delegate as? AppDelegate)?.saveContext() } } + diff --git a/iBox/Sources/Settings/Guide/GuideView.swift b/iBox/Sources/Settings/Guide/GuideView.swift deleted file mode 100644 index 7162049..0000000 --- a/iBox/Sources/Settings/Guide/GuideView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// GuideView.swift -// iBox -// -// Created by jiyeon on 4/22/24. -// - -import UIKit -import WebKit - -import SnapKit - -class GuideView: UIView { - - var guideUrl: URL? { - didSet { - loadWebsite() - } - } - - // MARK: - UI Components - - private let webView: WKWebView - - // MARK: - Initializer - - override init(frame: CGRect) { - let config = WKWebViewConfiguration() - config.allowsInlineMediaPlayback = true - - webView = WKWebView(frame: .zero, configuration: config) - super.init(frame: frame) - - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - webView.stopLoading() - webView.isOpaque = false - } - - // MARK: - Setup Methods - - private func setupHierarchy() { - addSubview(webView) - } - - private func setupLayout() { - webView.snp.makeConstraints { make in - make.top.equalTo(self.safeAreaLayoutGuide.snp.topMargin) - make.bottom.equalTo(self.safeAreaLayoutGuide.snp.bottomMargin) - make.leading.equalTo(self.safeAreaLayoutGuide.snp.leadingMargin) - make.trailing.equalTo(self.safeAreaLayoutGuide.snp.trailingMargin) - } - } - - private func loadWebsite() { - guard let url = guideUrl else { return } - webView.load(URLRequest(url: url)) - webView.allowsBackForwardNavigationGestures = true - } - -} diff --git a/iBox/Sources/Settings/Guide/GuideViewController.swift b/iBox/Sources/Settings/Guide/GuideViewController.swift deleted file mode 100644 index bc393b5..0000000 --- a/iBox/Sources/Settings/Guide/GuideViewController.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// GuideViewController.swift -// iBox -// -// Created by jiyeon on 4/22/24. -// - -import UIKit - -class GuideViewController: BaseViewController, BaseViewControllerProtocol { - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupNavigationBar() - - guard let contentView = contentView as? GuideView else { return } - contentView.guideUrl = URL(string: "https://github.com/42Box/iOS/tree/develop?tab=readme-ov-file#%EF%B8%8F%EF%B8%8F-introduction") - } - - // MARK: - BaseViewControllerProtocol - - func setupNavigationBar() { - setNavigationBarTitleLabelText("μ•± μ†Œκ°œ") - setNavigationBarTitleLabelFont(.subTitlefont) - setNavigationBarBackButtonHidden(false) - } - -} diff --git a/iBox/Sources/Settings/HomeTab/HomeTabSelectorCell.swift b/iBox/Sources/Settings/HomeTab/HomeTabSelectorCell.swift deleted file mode 100644 index f22df25..0000000 --- a/iBox/Sources/Settings/HomeTab/HomeTabSelectorCell.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// HomeTabSelectorCell.swift -// iBox -// -// Created by jiyeon on 2/22/24. -// - -import UIKit - -import SnapKit - -class HomeTabSelectorCell: UITableViewCell { - - static let reuseIdentifier = "MainTabCell" - - // MARK: - UI Components - - let titleLabel = UILabel().then { - $0.font = .cellTitleFont - } - - let selectButton = UIButton().then { - $0.configuration = .plain() - } - - // MARK: - Initializer - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .clear - selectionStyle = .none - } - - private func setupHierarchy() { - addSubview(titleLabel) - addSubview(selectButton) - } - - private func setupLayout() { - titleLabel.snp.makeConstraints { make in - make.leading.equalToSuperview().inset(20) - make.centerY.equalToSuperview() - } - - selectButton.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(20) - make.centerY.equalToSuperview() - make.width.height.equalTo(20) - } - } - - func setupSelectButton(_ selected: Bool) { - if selected { - selectButton.configuration?.image = UIImage(systemName: "circle.inset.filled") - selectButton.tintColor = .box2 - } else { - selectButton.configuration?.image = UIImage(systemName: "circle") - selectButton.tintColor = .gray - } - } - -} diff --git a/iBox/Sources/Settings/HomeTab/HomeTabSelectorView.swift b/iBox/Sources/Settings/HomeTab/HomeTabSelectorView.swift deleted file mode 100644 index c8ba4df..0000000 --- a/iBox/Sources/Settings/HomeTab/HomeTabSelectorView.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// HomeTabSelectorView.swift -// iBox -// -// Created by jiyeon on 2/22/24. -// - -import Combine -import UIKit - -class HomeTabSelectorView: UIView { - - private var viewModel: HomeTabSelectorViewModel? - private var cancellables = Set() - - // MARK: - UI Components - - let tableView = UITableView().then { - $0.separatorStyle = .none - $0.register(HomeTabSelectorCell.self, forCellReuseIdentifier: HomeTabSelectorCell.reuseIdentifier) - $0.backgroundColor = .clear - } - - // 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() { - tableView.delegate = self - tableView.dataSource = self - } - - private func setupHierarchy() { - addSubview(tableView) - } - - private func setupLayout() { - tableView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - } - - // MARK: - Bind ViewModel - - func bindViewModel(_ viewModel: HomeTabSelectorViewModel) { - self.viewModel = viewModel - viewModel.$selectedIndex - .receive(on: RunLoop.main) - .sink { [weak self] selectedIndex in - UserDefaultsManager.homeTabIndex = selectedIndex - self?.tableView.reloadData() - }.store(in: &cancellables) - } - -} - -extension HomeTabSelectorView: UITableViewDelegate { - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 55 - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let viewModel = viewModel else { return } - viewModel.selectedIndex = indexPath.row - } - -} - -extension HomeTabSelectorView: UITableViewDataSource { - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return HomeTabType.allCases.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let viewModel = viewModel, - let cell = tableView.dequeueReusableCell(withIdentifier: HomeTabSelectorCell.reuseIdentifier) as? HomeTabSelectorCell else { return UITableViewCell() } - cell.titleLabel.text = HomeTabType.allCases[indexPath.row].toString() - cell.setupSelectButton(viewModel.selectedIndex == indexPath.row) - return cell - } - -} diff --git a/iBox/Sources/Settings/HomeTab/HomeTabSelectorViewController.swift b/iBox/Sources/Settings/HomeTab/HomeTabSelectorViewController.swift deleted file mode 100644 index b22096a..0000000 --- a/iBox/Sources/Settings/HomeTab/HomeTabSelectorViewController.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// HomeTabSelectorViewwController.swift -// iBox -// -// Created by jiyeon on 2/22/24. -// - -import UIKit - -class HomeTabSelectorViewController: BaseViewController, BaseViewControllerProtocol { - - private let viewModel = HomeTabSelectorViewModel() - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupNavigationBar() - - guard let contentView = contentView as? HomeTabSelectorView else { return } - contentView.bindViewModel(viewModel) - } - - // MARK: - BaseViewControllerProtocol - - func setupNavigationBar() { - setNavigationBarTitleLabelText("μ‹œμž‘ ν™”λ©΄ μ„€μ •ν•˜κΈ°") - setNavigationBarTitleLabelFont(.subTitlefont) - setNavigationBarBackButtonHidden(false) - } - -} diff --git a/iBox/Sources/Settings/HomeTab/HomeTabSelectorViewModel.swift b/iBox/Sources/Settings/HomeTab/HomeTabSelectorViewModel.swift deleted file mode 100644 index 6a49529..0000000 --- a/iBox/Sources/Settings/HomeTab/HomeTabSelectorViewModel.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// HomeTabSelectorViewModel.swift -// iBox -// -// Created by jiyeon on 2/22/24. -// - -import Combine -import Foundation - -class HomeTabSelectorViewModel { - - @Published var selectedIndex: Int = UserDefaultsManager.homeTabIndex - -} diff --git a/iBox/Sources/Settings/Reset/ResetSuccessView.swift b/iBox/Sources/Settings/Reset/ResetSuccessView.swift deleted file mode 100644 index c72685d..0000000 --- a/iBox/Sources/Settings/Reset/ResetSuccessView.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// ResetSuccessView.swift -// iBox -// -// Created by Chan on 4/17/24. -// - -import UIKit - -import SnapKit - -protocol ResetSuccessViewDelegate: AnyObject { - func didCompleteReset() -} - -class ResetSuccessView: UIView { - - weak var delegate: ResetSuccessViewDelegate? - - private let checkMarkImageView = UIImageView().then { - $0.image = UIImage(systemName: "checkmark") - $0.contentMode = .scaleAspectFit - $0.tintColor = .box2 - } - - private let label = UILabel().then { - $0.text = "μ΄ˆκΈ°ν™” 성곡" - $0.textColor = .invertBackgroundColor - $0.textAlignment = .center - $0.font = UIFont.systemFont(ofSize: 16) - } - - private let stackView = UIStackView().then { - $0.axis = .vertical - $0.spacing = 10 - $0.alignment = .center - $0.distribution = .equalSpacing - } - - private let backPannelView = UIView().then { - $0.backgroundColor = .backgroundColor - $0.layer.cornerRadius = 20 - $0.clipsToBounds = true - } - - override init(frame: CGRect) { - super.init(frame: frame) - setupView() - animateView() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupView() { - self.alpha = 0 - self.backgroundColor = UIColor.invertBackgroundColor.withAlphaComponent(0.35) - self.addSubview(backPannelView) - backPannelView.addSubview(stackView) - - stackView.addArrangedSubview(checkMarkImageView) - stackView.addArrangedSubview(label) - - backPannelView.snp.makeConstraints { make in - make.width.height.equalTo(200) - make.center.equalToSuperview() - } - - stackView.snp.makeConstraints { make in - make.center.equalToSuperview() - } - - checkMarkImageView.snp.makeConstraints { make in - make.width.height.equalTo(50) - } - } - - private func animateView() { - UIView.animate(withDuration: 0.5, animations: { - self.alpha = 1.0 - }) { _ in - UIView.animate(withDuration: 0.5) { - self.backPannelView.alpha = 1.0 - } - - UIView.animate(withDuration: 0.5, delay: 2, options: []) { - self.backPannelView.alpha = 0.0 - } completion: { _ in - UIView.animate(withDuration: 0.5, animations: { - self.alpha = 0.0 - }) { _ in - self.removeFromSuperview() - self.delegate?.didCompleteReset() - } - } - } - } -} diff --git a/iBox/Sources/Settings/Reset/ResetView.swift b/iBox/Sources/Settings/Reset/ResetView.swift deleted file mode 100644 index 303f22f..0000000 --- a/iBox/Sources/Settings/Reset/ResetView.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// ResetView.swift -// iBox -// -// Created by jiyeon on 3/14/24. -// - -import UIKit - -import SnapKit - -class ResetView: UIView { - - var delegate: ResetViewDelegate? - - // MARK: - UI Components - - let label = UILabel().then { - $0.text = "κ²½κ³ : 이 μž‘μ—…μ„ μ§„ν–‰ν•˜λ©΄ μ €μž₯ν•˜μ‹  λͺ¨λ“  폴더 및 뢁마크 정보가 영ꡬ적으둜 μ‚­μ œλ˜κ³  κΈ°λ³Έκ°’μœΌλ‘œ μ΄ˆκΈ°ν™”λ©λ‹ˆλ‹€. μ§„ν–‰ν•˜κΈ° 전에 μ€‘μš”ν•œ 정보가 μ—†λŠ”μ§€ λ‹€μ‹œ ν•œλ²ˆ 확인해 μ£Όμ‹œκΈ° λ°”λžλ‹ˆλ‹€." - $0.numberOfLines = 0 - $0.font = .descriptionFont - } - - let resetButton = UIButton().then { - $0.configuration = .plain() - $0.configuration?.attributedTitle = .init("μ΄ˆκΈ°ν™”", attributes: .init([.font: UIFont.boldLabelFont])) - $0.tintColor = .white - $0.backgroundColor = .box - $0.clipsToBounds = true - $0.layer.cornerRadius = 5 - } - - // 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() { - resetButton.addTarget(self, action: #selector(handleResetButtonTap), for: .touchUpInside) - } - - private func setupHierarchy() { - addSubview(label) - addSubview(resetButton) - } - - private func setupLayout() { - label.snp.makeConstraints { make in - make.top.leading.trailing.equalToSuperview().inset(20) - } - - resetButton.snp.makeConstraints { make in - make.top.equalTo(label.snp.bottom).offset(20) - make.trailing.leading.equalToSuperview().inset(20) - make.height.equalTo(44) - } - } - - @objc private func handleResetButtonTap() { - delegate?.showAlert() - } - -} diff --git a/iBox/Sources/Settings/Reset/ResetViewController.swift b/iBox/Sources/Settings/Reset/ResetViewController.swift deleted file mode 100644 index c5e10bc..0000000 --- a/iBox/Sources/Settings/Reset/ResetViewController.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// ResetViewController.swift -// iBox -// -// Created by jiyeon on 3/14/24. -// - -import UIKit - -protocol ResetViewDelegate { - func showAlert() -} - -class ResetViewController: BaseViewController, BaseViewControllerProtocol { - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupNavigationBar() - - guard let contentView = contentView as? ResetView else { return } - contentView.delegate = self - } - - // MARK: - BaseViewControllerProtocol - - func setupNavigationBar() { - setNavigationBarTitleLabelText("데이터 μ΄ˆκΈ°ν™”") - setNavigationBarTitleLabelFont(.subTitlefont) - setNavigationBarBackButtonHidden(false) - } - -} - -extension ResetViewController: ResetViewDelegate { - - func showAlert() { - let alertController = UIAlertController(title: "κ²½κ³ ", message: "이 μž‘μ—…μ€ 되돌릴 수 μ—†μŠ΅λ‹ˆλ‹€. κ³„μ†ν•˜λ €λ©΄ \"iBox\"라고 μž…λ ₯ν•΄ μ£Όμ„Έμš”.", preferredStyle: .alert) - - let cancelAction = UIAlertAction(title: "μ·¨μ†Œ", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - - let confirmAction = UIAlertAction(title: "확인", style: .default) { [weak self] _ in - guard let self = self else { return } - if let textField = alertController.textFields?.first, let text = textField.text, text == "iBox" { - self.resetData() - } else { - self.showAlert() - } - } - - confirmAction.isEnabled = false - confirmAction.setValue(UIColor.red, forKey: "titleTextColor") - - alertController.addAction(confirmAction) - - alertController.addTextField() { textField in - NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: textField, queue: OperationQueue.main, using: - {_ in - let isTextMatch = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "iBox" - - confirmAction.isEnabled = isTextMatch - }) - - } - - self.present(alertController, animated: true, completion: nil) - } - - private func resetData() { - DefaultData.insertDefaultDataIfNeeded(true) { - UserDefaultsManager.favoriteId = nil - WebViewPreloader.shared.setFavoriteUrl(url: nil) - NotificationCenter.default.post(name: .didResetData, object: nil) - DispatchQueue.main.async { - let successView = ResetSuccessView(frame: self.view.bounds) - successView.delegate = self - self.view.addSubview(successView) - successView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - } - } - } -} - -// MARK: - ResetSuccessView - -extension ResetViewController: ResetSuccessViewDelegate { - - func didCompleteReset() { - self.navigationController?.popViewController(animated: true) - } - -} diff --git a/iBox/Sources/Settings/SettingsCellViewModel.swift b/iBox/Sources/Settings/SettingsCellViewModel.swift deleted file mode 100644 index bd39065..0000000 --- a/iBox/Sources/Settings/SettingsCellViewModel.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// SettingsCellViewModel.swift -// iBox -// -// Created by jiyeon on 2/22/24. -// - -import Foundation - -class SettingsCellViewModel { - - let settingsItem: SettingsItem - - init(_ settingsItem: SettingsItem) { - self.settingsItem = settingsItem - } - - var title: String { - settingsItem.type.toString() - } - - var flag: Bool? { - settingsItem.flag - } - - var description: String? { - settingsItem.description - } - -} diff --git a/iBox/Sources/Settings/SettingsItemCell.swift b/iBox/Sources/Settings/SettingsItemCell.swift deleted file mode 100644 index f3a239c..0000000 --- a/iBox/Sources/Settings/SettingsItemCell.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// SettingsItemCell.swift -// iBox -// -// Created by jiyeon on 1/3/24. -// - -import UIKit - -import SnapKit - -class SettingsItemCell: UITableViewCell { - - static let reuseIdentifier = "SettingsItemCell" - private var viewModel: SettingsCellViewModel? - - // MARK: - UI Components - - let titleLabel = UILabel().then { - $0.font = .cellTitleFont - } - - let descriptionLabel = UILabel().then { - $0.font = .cellDescriptionFont - $0.textColor = .gray - } - - let switchControl = UISwitch().then { - $0.onTintColor = .box2 - } - - let chevronButton = UIButton().then { - $0.configuration = .plain() - $0.configuration?.image = UIImage(systemName: "chevron.right") - $0.configuration?.preferredSymbolConfigurationForImage = .init(pointSize: 10, weight: .bold) - $0.tintColor = .systemGray3 - $0.isUserInteractionEnabled = false - } - - // MARK: - Initializer - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .clear - selectionStyle = .none - } - - private func setupHierarchy() { - contentView.addSubview(titleLabel) - contentView.addSubview(switchControl) - contentView.addSubview(descriptionLabel) - contentView.addSubview(chevronButton) - } - - private func setupLayout() { - titleLabel.snp.makeConstraints { make in - make.leading.equalToSuperview().inset(20) - make.centerY.equalToSuperview() - } - - switchControl.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(30) - make.centerY.equalToSuperview() - } - - descriptionLabel.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(30) - make.centerY.equalToSuperview() - } - - chevronButton.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(20) - make.centerY.equalToSuperview() - } - } - - // MARK: - Bind ViewModel - - func bindViewModel(_ viewModel: SettingsCellViewModel) { - self.viewModel = viewModel - titleLabel.text = viewModel.title - - descriptionLabel.isHidden = true - switchControl.isHidden = true - chevronButton.isHidden = true - - if let description = viewModel.description { - descriptionLabel.text = description - descriptionLabel.isHidden = false - } else if let flag = viewModel.flag { - switchControl.isOn = flag - switchControl.isHidden = false - } else { - chevronButton.isHidden = false - } - } - -} diff --git a/iBox/Sources/Settings/SettingsSectionViewModel.swift b/iBox/Sources/Settings/SettingsSectionViewModel.swift deleted file mode 100644 index 63efe2f..0000000 --- a/iBox/Sources/Settings/SettingsSectionViewModel.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// SettingsSectionViewModel.swift -// iBox -// -// Created by jiyeon on 2/22/24. -// - -import Foundation - -class SettingsSectionViewModel { - - let cellViewModels: [SettingsCellViewModel] - - init(cellViewModels: [SettingsCellViewModel]) { - self.cellViewModels = cellViewModels - } - -} diff --git a/iBox/Sources/Settings/SettingsView.swift b/iBox/Sources/Settings/SettingsView.swift deleted file mode 100644 index 24a151b..0000000 --- a/iBox/Sources/Settings/SettingsView.swift +++ /dev/null @@ -1,131 +0,0 @@ -// -// SettingsView.swift -// iBox -// -// Created by jiyeon on 1/3/24. -// - -import Combine -import UIKit - -final class SettingsView: UIView { - - var delegate: SettingsViewDelegate? - private var viewModel: SettingsViewModel? - private var cancellables = Set() - - // MARK: - UI Components - - let tableView = UITableView().then { - $0.register(SettingsItemCell.self, forCellReuseIdentifier: SettingsItemCell.reuseIdentifier) - $0.separatorStyle = .none - $0.sectionHeaderTopPadding = 0 - $0.backgroundColor = .clear - } - - // 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() { - tableView.delegate = self - tableView.dataSource = self - } - - private func setupHierarchy() { - addSubview(tableView) - } - - private func setupLayout() { - tableView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - } - - // MARK: - Bind ViewModel - - func bindViewModel(_ viewModel: SettingsViewModel) { - self.viewModel = viewModel - viewModel.transform(input: viewModel.input.eraseToAnyPublisher()) - .receive(on: RunLoop.main) - .sink { [weak self] event in - switch event { - case .updateSectionViewModels: - self?.tableView.reloadData() - } - }.store(in: &cancellables) - } - - // MARK: - Action Functions - - @objc private func handleHapticsSwitchTap(_ controlSwitch: UISwitch) { - guard let viewModel = viewModel else { return } - viewModel.input.send(.setHaptics(controlSwitch.isOn)) - } - -} - -extension SettingsView: UITableViewDelegate { - - func numberOfSections(in tableView: UITableView) -> Int { - guard let viewModel = viewModel else { return 0 } - return viewModel.sectionViewModels.count - } - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let headerView = UIView() - headerView.backgroundColor = .backgroundColor - return headerView - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return 20 - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 55 - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let viewModel = viewModel else { return } - let settingsItem = viewModel.sectionViewModels[indexPath.section].cellViewModels[indexPath.row].settingsItem - if (settingsItem.type != SettingsType.haptics) { - delegate?.pushViewController(settingsItem.type) - } - } - -} - -extension SettingsView: UITableViewDataSource { - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - guard let viewModel = viewModel else { return 0 } - return viewModel.sectionViewModels[section].cellViewModels.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let viewModel = viewModel, - let cell = tableView.dequeueReusableCell(withIdentifier: SettingsItemCell.reuseIdentifier) - as? SettingsItemCell else { return UITableViewCell() } - let cellViewModel = viewModel.sectionViewModels[indexPath.section].cellViewModels[indexPath.row] - cell.bindViewModel(cellViewModel) - let settingsType = cellViewModel.settingsItem.type - if settingsType == .haptics { - cell.switchControl.removeTarget(nil, action: nil, for: .valueChanged) - cell.switchControl.addTarget(self, action: #selector(handleHapticsSwitchTap), for: .valueChanged) - } - return cell - } - -} diff --git a/iBox/Sources/Settings/SettingsViewController.swift b/iBox/Sources/Settings/SettingsViewController.swift deleted file mode 100644 index 13ff56e..0000000 --- a/iBox/Sources/Settings/SettingsViewController.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// SettingsViewController.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 12/27/23. -// - -import UIKit - -protocol SettingsViewDelegate { - func pushViewController(_ type: SettingsType) - func pushViewController(_ viewController: UIViewController) -} - -final class SettingsViewController: BaseViewController, BaseViewControllerProtocol { - - private let viewModel = SettingsViewModel() - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupNavigationBar() - - guard let contentView = contentView as? SettingsView else { return } - contentView.delegate = self - contentView.bindViewModel(viewModel) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - viewModel.input.send(.viewWillAppear) - } - - // MARK: - BaseViewControllerProtocol - - func setupNavigationBar() { - setNavigationBarTitleLabelText("μ„€μ •") - } - -} - -extension SettingsViewController: SettingsViewDelegate { - - func pushViewController(_ type: SettingsType) { - switch type { - case .theme: - navigationController?.pushViewController(ThemeViewController(), animated: true) - case .homeTab: - navigationController?.pushViewController(HomeTabSelectorViewController(), animated: true) - case .reset: - navigationController?.pushViewController(ResetViewController(), animated: true) - case .guide: - navigationController?.pushViewController(GuideViewController(), animated: true) - default: break - } - } - - func pushViewController(_ viewController: UIViewController) { - navigationController?.pushViewController(viewController, animated: true) - } - -} diff --git a/iBox/Sources/Settings/SettingsViewModel.swift b/iBox/Sources/Settings/SettingsViewModel.swift deleted file mode 100644 index e70e9ec..0000000 --- a/iBox/Sources/Settings/SettingsViewModel.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// SettingsViewModel.swift -// iBox -// -// Created by jiyeon on 2/22/24. -// - -import Combine -import Foundation - -class SettingsViewModel { - - enum Input { - case viewWillAppear - case setHaptics(_ isOn: Bool) - } - - enum Output { - case updateSectionViewModels - } - - // MARK: - Properties - - let input = PassthroughSubject() - private let output = PassthroughSubject() - private var cancellables = Set() - var sectionViewModels = [SettingsSectionViewModel]() - - func transform(input: AnyPublisher) -> AnyPublisher { - input.sink { [weak self] event in - switch event { - case .viewWillAppear: - self?.sectionViewModels.removeAll() - self?.updateSectionViewModels() - self?.output.send(.updateSectionViewModels) - case let .setHaptics(isOn): - UserDefaultsManager.isHaptics = isOn - } - }.store(in: &cancellables) - return output.eraseToAnyPublisher() - } - - private func updateSectionViewModels() { - sectionViewModels.append(SettingsSectionViewModel(cellViewModels: [ - SettingsCellViewModel(SettingsItem(type: .theme, description: UserDefaultsManager.theme.toString())), - SettingsCellViewModel(SettingsItem(type: .homeTab, description: HomeTabType.allCases[UserDefaultsManager.homeTabIndex].toString())), - SettingsCellViewModel(SettingsItem(type: .haptics, flag: UserDefaultsManager.isHaptics)) - ])) - sectionViewModels.append(SettingsSectionViewModel(cellViewModels: [ - SettingsCellViewModel(SettingsItem(type: .reset)), - SettingsCellViewModel(SettingsItem(type: .guide)) - ])) - } - -} diff --git a/iBox/Sources/Settings/Theme/ThemeCell.swift b/iBox/Sources/Settings/Theme/ThemeCell.swift deleted file mode 100644 index 4a4c9fe..0000000 --- a/iBox/Sources/Settings/Theme/ThemeCell.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// ThemeCell.swift -// iBox -// -// Created by jiyeon on 1/3/24. -// - -import UIKit - -class ThemeCell: UITableViewCell { - - static let reuseIdentifier = "ThemeCell" - - // MARK: - UI Components - - let themeImageView = UIImageView().then { - $0.tintColor = .label - } - - let titleLabel = UILabel().then { - $0.font = .cellTitleFont - } - - let selectButton = UIButton().then { - $0.configuration = .plain() - } - - // MARK: - Initializer - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .clear - selectionStyle = .none - } - - private func setupHierarchy() { - addSubview(themeImageView) - addSubview(titleLabel) - addSubview(selectButton) - } - - private func setupLayout() { - themeImageView.snp.makeConstraints { make in - make.leading.equalToSuperview().inset(20) - make.centerY.equalToSuperview() - make.width.height.equalTo(23) - } - - titleLabel.snp.makeConstraints { make in - make.leading.equalTo(themeImageView.snp.trailing).offset(10) - make.centerY.equalToSuperview() - } - - selectButton.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(20) - make.centerY.equalToSuperview() - make.width.height.equalTo(20) - } - } - - func bind(_ theme: Theme) { - titleLabel.text = theme.toString() - themeImageView.image = UIImage(systemName: theme.toImageString()) - } - - func setupSelectButton(_ selected: Bool) { - if selected { - selectButton.configuration?.image = UIImage(systemName: "circle.inset.filled") - selectButton.tintColor = .box2 - } else { - selectButton.configuration?.image = UIImage(systemName: "circle") - selectButton.tintColor = .gray - } - } - -} diff --git a/iBox/Sources/Settings/Theme/ThemeView.swift b/iBox/Sources/Settings/Theme/ThemeView.swift deleted file mode 100644 index 3b68fb9..0000000 --- a/iBox/Sources/Settings/Theme/ThemeView.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// ThemeView.swift -// iBox -// -// Created by jiyeon on 1/3/24. -// - -import Combine -import UIKit - -import SnapKit - -class ThemeView: UIView { - - private var viewModel: ThemeViewModel? - private var cancellables = Set() - - // MARK: - UI Components - - let tableView = UITableView().then { - $0.register(ThemeCell.self, forCellReuseIdentifier: ThemeCell.reuseIdentifier) - $0.separatorStyle = .none - $0.sectionHeaderTopPadding = 0 - $0.backgroundColor = .clear - } - - // 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() { - tableView.delegate = self - tableView.dataSource = self - } - - private func setupHierarchy() { - addSubview(tableView) - } - - private func setupLayout() { - tableView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - } - - // MARK: - Bind ViewModel - - func bineViewModel(_ viewModel: ThemeViewModel) { - self.viewModel = viewModel - viewModel.$selectedIndex - .receive(on: RunLoop.main) - .sink { [weak self] selectedIndex in - guard let window = self?.window else { return } - UserDefaultsManager.theme = Theme.allCases[selectedIndex] - window.overrideUserInterfaceStyle = self?.toUserInterfaceStyle(UserDefaultsManager.theme) ?? .unspecified - self?.tableView.reloadData() - }.store(in: &cancellables) - } - -} - -extension ThemeView: UITableViewDelegate { - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 55 - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let viewModel = viewModel else { return } - viewModel.selectedIndex = indexPath.row - } - -} - -extension ThemeView: UITableViewDataSource { - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return Theme.allCases.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let viewModel = viewModel, - let cell = tableView.dequeueReusableCell(withIdentifier: ThemeCell.reuseIdentifier) - as? ThemeCell else { return UITableViewCell() } - let theme = Theme.allCases[indexPath.row] - cell.bind(theme) - cell.setupSelectButton(viewModel.selectedIndex == indexPath.row) - return cell - } - -} diff --git a/iBox/Sources/Settings/Theme/ThemeViewController.swift b/iBox/Sources/Settings/Theme/ThemeViewController.swift deleted file mode 100644 index 463805f..0000000 --- a/iBox/Sources/Settings/Theme/ThemeViewController.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// ThemeViewController.swift -// iBox -// -// Created by jiyeon on 1/3/24. -// - -import UIKit - -class ThemeViewController: BaseViewController, BaseViewControllerProtocol { - - private let viewModel = ThemeViewModel() - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupNavigationBar() - - guard let contentView = contentView as? ThemeView else { return } - contentView.bineViewModel(viewModel) - } - - // MARK: - BaseViewControllerProtocol - - func setupNavigationBar() { - setNavigationBarTitleLabelText("닀크 λͺ¨λ“œ μ„€μ •") - setNavigationBarTitleLabelFont(.subTitlefont) - setNavigationBarBackButtonHidden(false) - } - -} diff --git a/iBox/Sources/Settings/Theme/ThemeViewModel.swift b/iBox/Sources/Settings/Theme/ThemeViewModel.swift deleted file mode 100644 index 95aabb2..0000000 --- a/iBox/Sources/Settings/Theme/ThemeViewModel.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// ThemeViewModel.swift -// iBox -// -// Created by jiyeon on 2/22/24. -// - -import Combine -import Foundation - -class ThemeViewModel { - - @Published var selectedIndex: Int - - init() { - switch UserDefaultsManager.theme { - case .light: selectedIndex = 0 - case .dark: selectedIndex = 1 - case .system: selectedIndex = 2 - } - } - -} diff --git a/iBox/Sources/Shared/AddBookmarkManager.swift b/iBox/Sources/Shared/AddBookmarkManager.swift deleted file mode 100644 index fbb7f70..0000000 --- a/iBox/Sources/Shared/AddBookmarkManager.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// URLDataManager.swift -// iBox -// -// Created by μ΅œμ’…μ› on 3/5/24. -// - -import UIKit - -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 - } - } - - 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 { - self.incomingError = .parseError - } - } - - private func extractDataParameter(from url: URL) -> String? { - let urlString = url.absoluteString - - guard let range = urlString.range(of: "url?data=") else { - return nil - } - - let dataParameter = urlString[range.upperBound...] - return String(dataParameter).removingPercentEncoding - } - - 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 { - self?.incomingError = .htmlError - return - } - - let encodingName = (response as? HTTPURLResponse)?.textEncodingName ?? "utf-8" - let encoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString))) - - guard let html = String(data: data, encoding: encoding) else { - self?.incomingError = .decodeError - 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 } - - incomingTitle = nil - incomingData = nil - incomingFaviconUrl = nil - isFetching = true - - - if urlString.hasPrefix("http://") { - update(with: (nil, urlString, nil)) - } else { - guard let url = URL(string: urlString) else { - isFetching = false - 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/Animator/FullSizePresentationController.swift b/iBox/Sources/Shared/Animator/FullSizePresentationController.swift deleted file mode 100644 index 8693cab..0000000 --- a/iBox/Sources/Shared/Animator/FullSizePresentationController.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// FullSizePresentationController.swift -// iBox -// -// Created by Chan on 4/24/24. -// - -import UIKit - -class FullSizePresentationController: UIPresentationController { - private var dimmingView: UIView! - - override func containerViewDidLayoutSubviews() { - super.containerViewDidLayoutSubviews() - - dimmingView.frame = containerView?.bounds ?? CGRect.zero - presentedView?.frame = frameOfPresentedViewInContainerView - } - - override func presentationTransitionWillBegin() { - guard let containerView = containerView else { return } - - dimmingView = UIView(frame: containerView.bounds) - dimmingView.backgroundColor = UIColor(white: 0.0, alpha: 0.4) - dimmingView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - - containerView.addSubview(dimmingView) - containerView.sendSubviewToBack(dimmingView) - - super.presentationTransitionWillBegin() - } - - override var frameOfPresentedViewInContainerView: CGRect { - guard let containerView = containerView else { return .zero } - return CGRect(x: 0, y: 0, width: containerView.bounds.width, height: containerView.bounds.height) - } -} diff --git a/iBox/Sources/Shared/Animator/SlideInPresentationAnimator.swift b/iBox/Sources/Shared/Animator/SlideInPresentationAnimator.swift deleted file mode 100644 index f003178..0000000 --- a/iBox/Sources/Shared/Animator/SlideInPresentationAnimator.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// SlideInPresentationAnimator.swift -// iBox -// -// Created by Chan on 4/24/24. -// - -import UIKit - -class SlideInPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning { - let isPresentation: Bool - - init(isPresentation: Bool) { - self.isPresentation = isPresentation - super.init() - } - - func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.3 - } - - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - let key = isPresentation ? UITransitionContextViewControllerKey.to : UITransitionContextViewControllerKey.from - guard let controller = transitionContext.viewController(forKey: key) else { return } - - if isPresentation { - transitionContext.containerView.addSubview(controller.view) - } - - let presentedFrame = transitionContext.finalFrame(for: controller) - var dismissedFrame = presentedFrame - dismissedFrame.origin.y = -presentedFrame.height - - let initialFrame = isPresentation ? dismissedFrame : presentedFrame - let finalFrame = isPresentation ? presentedFrame : dismissedFrame - - controller.view.frame = initialFrame - UIView.animate( - withDuration: transitionDuration(using: transitionContext), - animations: { - controller.view.frame = finalFrame - }, completion: { finished in - transitionContext.completeTransition(finished) - }) - } -} diff --git a/iBox/Sources/Shared/AppStateManager.swift b/iBox/Sources/Shared/AppStateManager.swift deleted file mode 100644 index 10f8b22..0000000 --- a/iBox/Sources/Shared/AppStateManager.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// AppStateManager.swift -// iBox -// -// Created by Chan on 3/2/24. -// - -import Combine - -class AppStateManager { - static let shared = AppStateManager() - - @Published var versionCheckCompleted: VersionCheckCode = .initial - var currentViewErrorState: ViewErrorCode = .normal - - func updateViewError(_ error: ViewErrorCode) { - currentViewErrorState = error - } -} diff --git a/iBox/Sources/Shared/CoreDataManager.swift b/iBox/Sources/Shared/CoreDataManager.swift deleted file mode 100644 index 28ef1c2..0000000 --- a/iBox/Sources/Shared/CoreDataManager.swift +++ /dev/null @@ -1,333 +0,0 @@ -// -// CoreDataManager.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 2/9/24. -// - -import CoreData -import Foundation - -class CoreDataManager { - static let shared = CoreDataManager() - - lazy var persistentContainer = { - let container = NSPersistentContainer(name: "iBox") - - container.loadPersistentStores { _, error in - if let error { - fatalError(error.localizedDescription) - } - } - - return container - }() - - private init() {} - - private var lastFolderOrder: Int64 = 0 - private var lastBookmarkOrder = [UUID: Int64]() - - private func save() { - guard persistentContainer.viewContext.hasChanges else { return } - do { - try persistentContainer.viewContext.save() - } catch { - print("Fail to save the context:", error.localizedDescription) - } - } -} - -// 폴더 κ΄€λ ¨ -extension CoreDataManager { - func addInitialFolders(_ folders: [Folder]) { - let context = persistentContainer.viewContext - - for folder in folders { - let newFolder = FolderEntity(context: context) - newFolder.id = folder.id - newFolder.name = folder.name - newFolder.order = lastFolderOrder - lastFolderOrder += 1 - let bookmarks = NSMutableOrderedSet() - lastBookmarkOrder[folder.id] = 0 - for bookmark in folder.bookmarks { - let newBookmark = BookmarkEntity(context: context) - newBookmark.id = bookmark.id - newBookmark.name = bookmark.name - newBookmark.url = bookmark.url - newBookmark.order = lastBookmarkOrder[folder.id] ?? 0 - lastBookmarkOrder[folder.id] = (lastBookmarkOrder[folder.id] ?? 0) + 1 - bookmarks.add(newBookmark) - } - newFolder.bookmarks = bookmarks - } - save() - } - - func addFolder(_ folder: Folder) { - let context = persistentContainer.viewContext - let newFolder = FolderEntity(context: context) - newFolder.id = folder.id - newFolder.name = folder.name - newFolder.order = lastFolderOrder - lastFolderOrder += 1 - let bookmarks = NSMutableOrderedSet() - lastBookmarkOrder[folder.id] = 0 - for bookmark in folder.bookmarks { - let newBookmark = BookmarkEntity(context: context) - newBookmark.id = bookmark.id - newBookmark.name = bookmark.name - newBookmark.url = bookmark.url - newBookmark.order = lastBookmarkOrder[folder.id] ?? 0 - lastBookmarkOrder[folder.id] = (lastBookmarkOrder[folder.id] ?? 0) + 1 - bookmarks.add(newBookmark) - } - newFolder.bookmarks = bookmarks - save() - } - - private func getFolderEntity(id: UUID) -> FolderEntity? { - let context = persistentContainer.viewContext - - let fetchRequest = FolderEntity.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "id == %@", id as NSUUID) - - do { - let results = try context.fetch(fetchRequest) - return results.first - } catch { - print(error.localizedDescription) - return nil - } - } - - private func getAllFolderEntity() -> [FolderEntity] { - let context = persistentContainer.viewContext - - let fetchRequest = FolderEntity.fetchRequest() - let sortDescriptor = NSSortDescriptor(key: "order", ascending: true) - fetchRequest.sortDescriptors = [sortDescriptor] - - do { - return try context.fetch(fetchRequest) - } catch { - print(error.localizedDescription) - return [] - } - } - - func getFolders() -> [Folder] { - let folderEntities = getAllFolderEntity() - var folders = [Folder]() - - lastFolderOrder = (folderEntities.last?.order ?? -1) + 1 - for folderEntity in folderEntities { - let bookmarkEntities = (folderEntity.bookmarks?.array as? [BookmarkEntity] ?? []).sorted { - $0.order < $1.order - } - guard let folderId = folderEntity.id else { return [] } - lastBookmarkOrder[folderId] = (bookmarkEntities.last?.order ?? -1) + 1 - let bookmarks = bookmarkEntities.map{ Bookmark(id: $0.id ?? UUID(), name: $0.name ?? "" , url: $0.url ?? URL(string: "")!) } - folders.append(Folder(id: folderEntity.id ?? UUID(), name: folderEntity.name ?? "", bookmarks: bookmarks)) - } - - return folders - } - - func deleteFolder(id: UUID) { - let context = persistentContainer.viewContext - - guard let folder = getFolderEntity(id: id) else { return } - let deletedOrder = folder.order - context.delete(folder) - - let subsequentFolderEntities = getAllFolderEntity().filter{ $0.order > deletedOrder } - for folderEntity in subsequentFolderEntities { - folderEntity.order -= 1 - } - lastFolderOrder -= 1 - save() - } - - func deleteAllFolders() { - let context = persistentContainer.viewContext - - let folders = getAllFolderEntity() - for folder in folders { - context.delete(folder) - } - save() - } - - func updateFolder(id: UUID, name: String) { - guard let folder = getFolderEntity(id: id) else { return } - folder.name = name - save() - } - - func moveFolder(from source: Int, to destination: Int) { - let folderEntities = getAllFolderEntity() - - if source < destination { - var startIndex = source + 1 - let endIndex = destination - var startOrder = folderEntities[source].order - while startIndex <= endIndex { - folderEntities[startIndex].order = startOrder - startOrder += 1 - startIndex += 1 - } - folderEntities[source].order = startOrder - } else if destination < source { - var startIndex = destination - let endIndex = source - 1 - var startOrder = folderEntities[destination].order + 1 - let newOrder = folderEntities[destination].order - while startIndex <= endIndex { - folderEntities[startIndex].order = startOrder - startOrder += 1 - startIndex += 1 - } - folderEntities[source].order = newOrder - } - save() - } - -} - -// 뢁마크 κ΄€λ ¨ -extension CoreDataManager { - - func getBookmarkUrl(_ bookmarkId: UUID) -> URL? { - let entity = getBookmarkEntity(id: bookmarkId) - if let entity { - return entity.url - } else { - return nil - } - } - - func addBookmark(_ bookmark: Bookmark, folderId: UUID) { - let context = persistentContainer.viewContext - - guard let folder = getFolderEntity(id: folderId) else { return } - let newBookmark = BookmarkEntity(context: context) - newBookmark.id = bookmark.id - newBookmark.name = bookmark.name - newBookmark.url = bookmark.url - guard let folderId = folder.id else { return } - newBookmark.order = lastBookmarkOrder[folderId] ?? 0 - lastBookmarkOrder[folderId] = (lastBookmarkOrder[folderId] ?? 0) + 1 - newBookmark.folder = folder - save() - } - - func updateBookmark(id: UUID, name: String, url: URL) { - guard let bookmark = getBookmarkEntity(id: id) else { return } - bookmark.name = name - bookmark.url = url - save() - } - - private func getBookmarkEntity(id: UUID) -> BookmarkEntity? { - let context = persistentContainer.viewContext - - let fetchRequest = BookmarkEntity.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "id == %@", id as NSUUID) - - do { - let results = try context.fetch(fetchRequest) - return results.first - } catch { - print(error.localizedDescription) - return nil - } - } - - func deleteBookmark(id: UUID) { - let context = persistentContainer.viewContext - - guard let bookmark = getBookmarkEntity(id: id) else { return } - let deletedOrder = bookmark.order - context.delete(bookmark) - - guard let folderId = bookmark.folder?.id else { return } - let subsequentBookmarkEntities = getAllBookmarkEntity(in: folderId).filter{ $0.order > deletedOrder } - for bookmarkEntity in subsequentBookmarkEntities { - bookmarkEntity.order -= 1 - } - lastBookmarkOrder[folderId] = (lastBookmarkOrder[folderId] ?? 1) - 1 - save() - } - - private func getAllBookmarkEntity(in folderId: UUID) -> [BookmarkEntity] { - let context = persistentContainer.viewContext - - guard let folder = getFolderEntity(id: folderId) else { return [] } - let fetchRequest = BookmarkEntity.fetchRequest() - let sortDescriptor = NSSortDescriptor(key: "order", ascending: true) - fetchRequest.predicate = NSPredicate(format: "folder == %@", folder) - fetchRequest.sortDescriptors = [sortDescriptor] - - do { - return try context.fetch(fetchRequest) - } catch { - print(error.localizedDescription) - return [] - } - } - - func deleteAllBookmarks(folderId: UUID) { - let context = persistentContainer.viewContext - - let bookmarks = getAllBookmarkEntity(in: folderId) - for bookmark in bookmarks { - context.delete(bookmark) - } - save() - } - - func moveBookmark(from source: IndexPath, to destination: IndexPath, srcId: UUID, destFolderId: UUID) { - if source.section == destination.section { - moveWithinSameFolder(from: source.row, to: destination.row, bookmarkId: srcId, folderId: destFolderId) - } else { - moveToDifferentFolder(from: source, to: destination, bookmarkId: srcId, destFolderId: destFolderId) - } - save() - } - - private func moveWithinSameFolder(from sourceIndex: Int, to destinationIndex: Int, bookmarkId: UUID, folderId: UUID) { - var bookmarks = getAllBookmarkEntity(in: folderId) - let movingBookmark = bookmarks.remove(at: sourceIndex) - - let adjustedDestinationIndex = sourceIndex < destinationIndex ? destinationIndex - 1 : destinationIndex - - bookmarks.insert(movingBookmark, at: adjustedDestinationIndex) - - for (index, bookmark) in bookmarks.enumerated() { - bookmark.order = Int64(index) - } - } - - private func moveToDifferentFolder(from source: IndexPath, to destination: IndexPath, bookmarkId: UUID, destFolderId: UUID) { - guard let movingBookmark = getBookmarkEntity(id: bookmarkId), - let srcFolder = movingBookmark.folder, - let srcFolderId = srcFolder.id else { return } - let srcBookmarks = getAllBookmarkEntity(in: srcFolderId) - let destBookmarks = getAllBookmarkEntity(in: destFolderId) - let destFolder = getFolderEntity(id: destFolderId) - - for bookmark in srcBookmarks where bookmark.order > movingBookmark.order { - bookmark.order -= 1 - } - - movingBookmark.folder = destFolder - movingBookmark.order = Int64(destination.row) - for bookmark in destBookmarks where bookmark.order >= movingBookmark.order { - bookmark.order += 1 - } - } - -} - diff --git a/iBox/Sources/Shared/NetworkManager.swift b/iBox/Sources/Shared/NetworkManager.swift deleted file mode 100644 index f05ac09..0000000 --- a/iBox/Sources/Shared/NetworkManager.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// NetworkManager.swift -// iBox -// -// Created by Chan on 4/2/24. -// - -import Foundation - -class NetworkManager { - static let shared = NetworkManager() - - private init() {} - - func fetchModel(from urlString: String, modelType: T.Type, completion: @escaping (Result) -> Void) { - guard let url = URL(string: urlString) else { - completion(.failure(NSError(domain: "NetworkManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]))) - return - } - - let task = URLSession.shared.dataTask(with: url) { data, response, error in - if let error = error { - completion(.failure(error)) - return - } - - guard let data = data else { - completion(.failure(NSError(domain: "NetworkManager", code: -2, userInfo: [NSLocalizedDescriptionKey: "No data received"]))) - return - } - - do { - let model = try JSONDecoder().decode(modelType, from: data) - completion(.success(model)) - } catch { - completion(.failure(error)) - } - } - - task.resume() - } -} diff --git a/iBox/Sources/Shared/SlideInPresentationManager.swift b/iBox/Sources/Shared/SlideInPresentationManager.swift deleted file mode 100644 index 6e51d0f..0000000 --- a/iBox/Sources/Shared/SlideInPresentationManager.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// SlideInPresentationManager.swift -// iBox -// -// Created by Chan on 4/23/24. -// - -import UIKit - -class SlideInPresentationManager: NSObject, UIViewControllerTransitioningDelegate { - func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { - return FullSizePresentationController(presentedViewController: presented, presenting: presenting) - } - - func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return SlideInPresentationAnimator(isPresentation: true) - } - - func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return SlideInPresentationAnimator(isPresentation: false) - } -} diff --git a/iBox/Sources/Shared/UserDefaultsManager.swift b/iBox/Sources/Shared/UserDefaultsManager.swift deleted file mode 100644 index 36c58bb..0000000 --- a/iBox/Sources/Shared/UserDefaultsManager.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// UserDefaultsManager.swift -// iBox -// -// Created by jiyeon on 1/8/24. -// - -import Foundation - -final class UserDefaultsManager { - - @UserDefaultsData(key: "theme", defaultValue: Theme.system) - static var theme: Theme - - @UserDefaultsData(key: "favoriteId", defaultValue: nil) - static var favoriteId: UUID? - - @UserDefaultsData(key: "homeTabIndex", defaultValue: 0) - static var homeTabIndex: Int - - @UserDefaultsData(key: "isDefaultDataInserted", defaultValue: false) - static var isDefaultDataInserted: Bool - - @UserDefaultsData(key: "isHaptics", defaultValue: true) - static var isHaptics: Bool - - @UserDefaultsData(key: "selectedFolderId", defaultValue: nil) - static var selectedFolderId: UUID? - -} - -@propertyWrapper -struct UserDefaultsData { - let key: String - let defaultValue: Value - var container: UserDefaults = .standard - - var wrappedValue: Value { - get { - guard let data = container.object(forKey: key) as? Data else { - return defaultValue - } - do { - let value = try JSONDecoder().decode(Value.self, from: data) - return value - } catch { - print("Error decoding UserDefaults data for key \(key): \(error)") - return defaultValue - } - } - set { - do { - let data = try JSONEncoder().encode(newValue) - container.set(data, forKey: key) - } catch { - print("Error encoding UserDefaults data for key \(key): \(error)") - } - } - } -} diff --git a/iBox/Sources/Shared/WebCacheManager.swift b/iBox/Sources/Shared/WebCacheManager.swift deleted file mode 100644 index c3123d3..0000000 --- a/iBox/Sources/Shared/WebCacheManager.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// WebCacheManager.swift -// iBox -// -// Created by jiyeon on 3/26/24. -// - -import UIKit - -class WebCacheManager { - - static let shared = WebCacheManager() - - private let cache = NSCache() - - private init() {} - - func cacheData(forKey uuid: UUID, viewController: WebViewController) { - let wrapper = UUIDWrapper(uuid: uuid) - cache.setObject(viewController, forKey: wrapper) - } - - func viewControllerForKey(_ uuid: UUID) -> WebViewController? { - let wrapper = UUIDWrapper(uuid: uuid) - return cache.object(forKey: wrapper) - } - - func removeViewControllerForKey(_ uuid: UUID) { - let wrapper = UUIDWrapper(uuid: uuid) - cache.removeObject(forKey: wrapper) - } -} - -class UUIDWrapper: NSObject { - let uuid: UUID - - init(uuid: UUID) { - self.uuid = uuid - } - - override func isEqual(_ object: Any?) -> Bool { - guard let other = object as? UUIDWrapper else { return false } - return uuid == other.uuid - } - - override var hash: Int { - return uuid.hashValue - } -} diff --git a/iBox/Sources/Shared/WebViewPreloader.swift b/iBox/Sources/Shared/WebViewPreloader.swift deleted file mode 100644 index 734c0df..0000000 --- a/iBox/Sources/Shared/WebViewPreloader.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// WebViewPreloader.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/18/24. -// - -import Foundation -import WebKit - -class WebViewPreloader { - static let shared = WebViewPreloader() - private var favoriteView: (url: URL, webView: WebView)? - private var defaultUrl = URL(string: "https://profile.intra.42.fr/")! - - private init() {} - - func preloadFavoriteView(url: URL?) { - let webView = WebView() - webView.isOpaque = false - webView.selectedWebsite = url ?? defaultUrl - favoriteView = (url ?? defaultUrl, webView) - } - - func getFavoriteView() -> WebView? { - return favoriteView?.webView - } - - func resetFavoriteView() { - guard let favoriteView else { return } - favoriteView.webView.selectedWebsite = favoriteView.url - } - - func setFavoriteUrl(url: URL?) { - if let favoriteView { - if url == favoriteView.url || - (url == nil && favoriteView.url == defaultUrl ) { - return - } else { - self.favoriteView?.url = url ?? defaultUrl - resetFavoriteView() - } - } else { - preloadFavoriteView(url: url) - } - } - -} diff --git a/iBox/Sources/Versioning/VersionCheckCode.swift b/iBox/Sources/Versioning/VersionCheckCode.swift deleted file mode 100644 index 37b250e..0000000 --- a/iBox/Sources/Versioning/VersionCheckCode.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// VersionCheckCode.swift -// iBox -// -// Created by Chan on 3/2/24. -// - -enum VersionCheckCode: Equatable { - case initial // μ΄ˆκΈ°κ°’ - case success // 성곡 - case later // λ‚˜μ€‘μ— (ab testing code) - case update // μ—…λ°μ΄νŠΈ (ab testing code) - case urlError // URL κ΄€λ ¨ μ—λŸ¬ - case networkError // λ„€νŠΈμ›Œν¬ μš”μ²­ μ‹€νŒ¨ - case decodingError // λ””μ½”λ”© μ‹€νŒ¨ - case versionOutdated(mandatoryUpdate: Bool, updateUrl: String) // 버전이 ꡬ버전일 λ•Œ - case serverError // μ„œλ²„ μ—λŸ¬ λ˜λŠ” 기타 μ—λŸ¬ - case internalSceneError // λ‚΄λΆ€ 씬 μ—λŸ¬ - case internalInfoError // λ‚΄λΆ€ 인포 μ—λŸ¬ - case maxRetryReached // μ΅œλŒ€ μž¬μ‹œλ„ 횟수 도달 -} diff --git a/iBox/Sources/Versioning/VersioningHandler.swift b/iBox/Sources/Versioning/VersioningHandler.swift deleted file mode 100644 index cccde8f..0000000 --- a/iBox/Sources/Versioning/VersioningHandler.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// VersioningHandler.swift -// iBox -// -// Created by Chan on 3/2/24. -// - -import UIKit - -class VersioningHandler { - - func checkAppVersion(retryCount: Int = 0, completion: @escaping (VersionCheckCode) -> Void) { - let maxRetryCount = 3 - let urlString = "https://raw.githubusercontent.com/42Box/versioning/main/db.json" - - NetworkManager.shared.fetchModel(from: urlString, modelType: VersionInfo.self) { result in - switch result { - case .success(let versionInfo): - guard let latestVersion = versionInfo.version.first?.latestVersion, - let minRequiredVersion = versionInfo.version.first?.minRequiredVersion else { - completion(.urlError) - return - } - - self.compareVersion(latestVersion: latestVersion, minRequiredVersion: minRequiredVersion, storeURL: versionInfo.url.storeUrl, completion: completion) - - case .failure(let error): - print("Error: \(error.localizedDescription)") - - if retryCount < maxRetryCount { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.checkAppVersion(retryCount: retryCount + 1, completion: completion) - } - } else { - completion(.maxRetryReached) - } - } - } - } - - func compareVersion(latestVersion: String, minRequiredVersion: String, storeURL: String, completion: @escaping (VersionCheckCode) -> Void) { - guard let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { - completion(.internalInfoError) - return - } - - if appVersion.compare(minRequiredVersion, options: .numeric) == .orderedAscending { - showAlertForUpdate(storeURL: storeURL, isMandatory: true, completion: completion) - } else if appVersion.compare(latestVersion, options: .numeric) == .orderedAscending { - showAlertForUpdate(storeURL: storeURL, isMandatory: false, completion: completion) - } else { - completion(.success) - } - } - - func showAlertForUpdate(storeURL: String, isMandatory: Bool, completion: @escaping (VersionCheckCode) -> Void) { - DispatchQueue.main.async { - guard let windowScene = UIApplication.shared.connectedScenes.first(where: { $0 is UIWindowScene }) as? UIWindowScene, - let rootViewController = windowScene.windows.first(where: { $0.isKeyWindow })?.rootViewController else { - completion(.internalSceneError) - return - } - - let message = isMandatory ? "μƒˆλ‘œμš΄ 버전이 ν•„μš”ν•©λ‹ˆλ‹€. μ—…λ°μ΄νŠΈ ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?" : "μƒˆλ‘œμš΄ 버전이 μžˆμŠ΅λ‹ˆλ‹€. μ—…λ°μ΄νŠΈ ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?" - let alert = UIAlertController(title: "μ—…λ°μ΄νŠΈ μ•Œλ¦Ό", message: message, preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: "μ—…λ°μ΄νŠΈ", style: .default, handler: { _ in - if let url = URL(string: storeURL), UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url) - completion(.update) - } - })) - - if !isMandatory { - alert.addAction(UIAlertAction(title: "λ‚˜μ€‘μ—", style: .cancel, handler: { _ in - completion(.later) - })) - } - - rootViewController.present(alert, animated: true) - } - } -} diff --git a/iBox/Sources/Web/RefreshControl.swift b/iBox/Sources/Web/RefreshControl.swift deleted file mode 100644 index 6f1a80f..0000000 --- a/iBox/Sources/Web/RefreshControl.swift +++ /dev/null @@ -1,119 +0,0 @@ -// -// RefreshControl.swift -// iBox -// -// Created by jiyeon on 4/4/24. -// - -import UIKit - -import SnapKit - -enum RefreshControlType { - case addBookmark - case refresh - case back -} - -class RefreshControl: UIView { - - var currentType: RefreshControlType? - - // MARK: - UI Components - - let addBookmarkButton = UIButton().then { - $0.configuration = .plain() - $0.tintColor = .label - $0.configuration?.image = UIImage(systemName: "bookmark.circle") - $0.configuration?.imagePadding = 10 - $0.configuration?.imagePlacement = .top - $0.configuration?.preferredSymbolConfigurationForImage = .init(pointSize: 20.0) - $0.configuration?.attributedTitle = AttributedString("뢁마크 μΆ”κ°€", attributes: AttributeContainer([NSAttributedString.Key.font: UIFont.refreshControlFont])) - $0.layer.cornerRadius = 15 - } - - let refreshButton = UIButton().then { - $0.configuration = .plain() - $0.tintColor = .label - $0.configuration?.image = UIImage(systemName: "arrow.clockwise.circle") - $0.configuration?.imagePadding = 10 - $0.configuration?.imagePlacement = .top - $0.configuration?.preferredSymbolConfigurationForImage = .init(pointSize: 20.0) - $0.configuration?.attributedTitle = AttributedString("μƒˆλ‘œκ³ μΉ¨", attributes: AttributeContainer([NSAttributedString.Key.font: UIFont.refreshControlFont])) - $0.layer.cornerRadius = 15 - } - - let backButton = UIButton().then { - $0.configuration = .plain() - $0.tintColor = .label - $0.configuration?.image = UIImage(systemName: "arrowshape.turn.up.backward.circle") - $0.configuration?.imagePadding = 10 - $0.configuration?.imagePlacement = .top - $0.configuration?.preferredSymbolConfigurationForImage = .init(pointSize: 20.0) - $0.configuration?.attributedTitle = AttributedString("처음으둜 이동", attributes: AttributeContainer([NSAttributedString.Key.font: UIFont.refreshControlFont])) - $0.layer.cornerRadius = 15 - } - - let stackView = UIStackView().then { - $0.axis = .horizontal - $0.distribution = .fillEqually - $0.spacing = 20 - } - - - // MARK: - Initializer - - override init(frame: CGRect) { - super.init(frame: frame) - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MAKR: - Setup Methods - - private func setupProperty() { - backgroundColor = .backgroundColor - isUserInteractionEnabled = true - } - - private func setupHierarchy() { - addSubview(stackView) - stackView.addArrangedSubview(addBookmarkButton) - stackView.addArrangedSubview(refreshButton) - stackView.addArrangedSubview(backButton) - } - - private func setupLayout() { - stackView.snp.makeConstraints { make in - make.leading.bottom.trailing.equalToSuperview().inset(20) - } - } - - func setSelected(_ type: RefreshControlType) { - if type == currentType { return } - currentType = type - clear() - switch type { - case .addBookmark: addBookmarkButton.backgroundColor = .tableViewBackgroundColor - case .refresh: refreshButton.backgroundColor = .tableViewBackgroundColor - case .back: backButton.backgroundColor = .tableViewBackgroundColor - } - if UserDefaultsManager.isHaptics { - let generator = UIImpactFeedbackGenerator(style: .light) - generator.prepare() - generator.impactOccurred() - } - } - - func clear() { - [addBookmarkButton, refreshButton, backButton].forEach { button in - button.backgroundColor = .clear - } - } - -} diff --git a/iBox/Sources/Web/WebView.swift b/iBox/Sources/Web/WebView.swift deleted file mode 100644 index 760425d..0000000 --- a/iBox/Sources/Web/WebView.swift +++ /dev/null @@ -1,233 +0,0 @@ -// -// WebView.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/4/24. -// - -import UIKit -import WebKit - -import SnapKit - -class WebView: UIView { - - var delegate: WebViewDelegate? - var errorDelegate: WebViewErrorDelegate? - var lastRequestedURL: URL? - - private var progressObserver: NSKeyValueObservation? - - var selectedWebsite: URL? { - didSet { - loadWebsite() - } - } - - private var refreshControlHeight: CGFloat = 120.0 - private var isActive = false - - // MARK: - UI Components - - - private let webView: WKWebView - - private let progressView = UIProgressView().then { - $0.progressViewStyle = .bar - $0.tintColor = .box2 - $0.sizeToFit() - } - - private var refreshControl: RefreshControl? - - // MARK: - Initializer - - override init(frame: CGRect) { - let config = WKWebViewConfiguration() - config.allowsInlineMediaPlayback = true - - webView = WKWebView(frame: .zero, configuration: config) - super.init(frame: frame) - - setupProperty() - setupHierarchy() - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - progressObserver?.invalidate() - webView.stopLoading() - webView.isOpaque = false - webView.navigationDelegate = nil - webView.scrollView.delegate = nil - } - - // MARK: - Setup Methods - - private func setupProperty() { - backgroundColor = .backgroundColor - webView.navigationDelegate = self - webView.uiDelegate = self - progressObserver = webView.observe(\.estimatedProgress, options: .new) { [weak self] webView, _ in - self?.progressView.setProgress(Float(webView.estimatedProgress), animated: true) - } - } - - private func setupHierarchy() { - addSubview(webView) - addSubview(progressView) - } - - private func setupLayout() { - webView.snp.makeConstraints { make in - make.top.equalTo(self.safeAreaLayoutGuide.snp.topMargin) - make.bottom.equalTo(self.safeAreaLayoutGuide.snp.bottomMargin) - make.leading.equalTo(self.safeAreaLayoutGuide.snp.leadingMargin) - make.trailing.equalTo(self.safeAreaLayoutGuide.snp.trailingMargin) - } - - progressView.snp.makeConstraints { make in - make.top.equalTo(self.safeAreaLayoutGuide.snp.topMargin) - make.leading.trailing.equalToSuperview() - } - } - - func setupRefreshControl() { - // pan gesture - let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleSwipe)) - panGestureRecognizer.delegate = self // UIGestureRecognizerDelegate - addGestureRecognizer(panGestureRecognizer) - // refresh control - let refreshControl = RefreshControl(frame: .init(x: 0, y: -frame.size.height, width: frame.size.width, height: frame.size.height)) - webView.scrollView.addSubview(refreshControl) - webView.scrollView.backgroundColor = .backgroundColor - webView.scrollView.delegate = self // UIScrollViewDelegate - self.refreshControl = refreshControl - } - - private func loadWebsite() { - guard let url = selectedWebsite else { return } - webView.load(URLRequest(url: url)) - webView.allowsBackForwardNavigationGestures = true - } - - @objc func handleSwipe(_ gesture: UIPanGestureRecognizer) { - guard isActive, let refreshControl = refreshControl else { return } - - let translation = gesture.translation(in: self) - if gesture.state == .changed { - if abs(translation.x) > 60.0 { - if translation.x > 0 { // 였λ₯Έμͺ½ μŠ€μ™€μ΄ν”„ : 처음 뢁마크둜 λŒμ•„κ°€κΈ° - refreshControl.setSelected(.back) - } else { // μ™Όμͺ½ μŠ€μ™€μ΄ν”„ : ν˜„μž¬ 링크 뢁마크 μΆ”κ°€ - refreshControl.setSelected(.addBookmark) - } - } else { // μ•„λž˜ : μƒˆλ‘œκ³ μΉ¨ - refreshControl.setSelected(.refresh) - } - } else if gesture.state == .ended { // μ‚¬μš©μžμ˜ ν„°μΉ˜κ°€ 끝났을 λ•Œ - switch refreshControl.currentType { - case .addBookmark: - guard let url = webView.url else { return } - delegate?.pushAddBookMarkViewController(url: url) - case .refresh: - self.webView.reload() - case .back: - loadWebsite() - case .none: break - } - // 제슀처 μ™„λ£Œ ν›„ μ΄ˆκΈ°ν™” - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - refreshControl.clear() - } - } - // 제슀처 μ΄ˆκΈ°ν™” - if gesture.state == .ended || gesture.state == .cancelled { - gesture.setTranslation(CGPoint.zero, in: self) - refreshControl.currentType = nil - } - } - -} - -extension WebView: WKNavigationDelegate { - - func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { - // λ‘œλ”© μ‹œμž‘ μ‹œ ν”„λ‘œκ·Έλ ˆμŠ€ λ°”λ₯Ό 보여주고 진행λ₯  μ΄ˆκΈ°ν™” - progressView.isHidden = false - progressView.setProgress(0.0, animated: false) - } - - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - progressView.setProgress(1.0, animated: true) - // μ•½κ°„μ˜ λ”œλ ˆμ΄ ν›„ ν”„λ‘œκ·Έλ ˆμŠ€ λ°”λ₯Ό μˆ¨κΉ€ - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.progressView.isHidden = true - } - } - - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - - // λ§ˆμ§€λ§‰μœΌλ‘œ μ‹œλ„ν•œ navigation url - lastRequestedURL = navigationAction.request.url - - // "μƒˆ 창으둜 μ—΄κΈ°" 링크 WebView λ‚΄μ—μ„œ μ—΄κΈ° - if navigationAction.targetFrame == nil { - webView.load(navigationAction.request) - } - decisionHandler(.allow) - } - - func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { - // 초기 λ‘œλ“œμ‹œ μ—λŸ¬ λ°œμƒ - errorDelegate?.webView(self, didFailWithError: error, url: selectedWebsite) - } - - func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - // λ„€λΉ„κ²Œμ΄μ…˜ 쀑 μ—λŸ¬ λ°œμƒ μ‹œ - if let lastURL = lastRequestedURL { - errorDelegate?.webView(self, didFailWithError: error, url: lastURL) - } else { - // lastRequestedURL이 nil인 경우 λŒ€λΉ„ - errorDelegate?.webView(self, didFailWithError: error, url: nil) - } - } - - func retryLoading() { - webView.reload() - } -} - -extension WebView: UIScrollViewDelegate { - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if scrollView.contentOffset.y < -refreshControlHeight { - isActive = true - } else { - isActive = false - } - } - -} - -extension WebView: UIGestureRecognizerDelegate { - - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - // λ‹€λ₯Έ 제슀처 인식기와 λ™μ‹œμ— μΈμ‹λ˜λ„λ‘ ν—ˆμš© - return true - } - -} - -extension WebView: WKUIDelegate { - func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { - if navigationAction.targetFrame == nil { - webView.load(navigationAction.request) - } - return nil - } -} diff --git a/iBox/Sources/Web/WebViewController.swift b/iBox/Sources/Web/WebViewController.swift deleted file mode 100644 index b872a1a..0000000 --- a/iBox/Sources/Web/WebViewController.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// WebViewController.swift -// iBox -// -// Created by μ΄μ§€ν˜„ on 1/4/24. -// - -import UIKit -import WebKit - -protocol WebViewDelegate { - func pushAddBookMarkViewController(url: URL) -} - -class WebViewController: BaseViewController, BaseViewControllerProtocol { - var id: UUID? - var slideInPresentationManager = SlideInPresentationManager() - var errorViewController: ErrorPageViewController? - var delegate: AddBookmarkViewControllerProtocol? - var selectedWebsite: URL? - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - - setupNavigationBar() - setupView() - setupDelegate() - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - guard let contentView = contentView as? WebView else { return } - contentView.setupRefreshControl() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - if ((isMovingFromParent || isBeingDismissed) && AppStateManager.shared.currentViewErrorState != .normal){ - if let id = self.id { - WebCacheManager.shared.removeViewControllerForKey(id) - } - } - } - - deinit { - AppStateManager.shared.currentViewErrorState = .normal - errorViewController = nil - } - - // MARK: - BaseViewControllerProtocol - - func setupNavigationBar() { - setNavigationBarHidden(true) - } - - func setupDelegate() { - guard let contentView = contentView as? WebView else { return } - contentView.delegate = self - contentView.selectedWebsite = selectedWebsite - - errorViewController = ErrorPageViewController(webView: contentView) - contentView.errorDelegate = errorViewController - errorViewController?.delegate = self - } - - func setupView() { - view.backgroundColor = .backgroundColor - } -} - -extension WebViewController: WebViewDelegate { - - func pushAddBookMarkViewController(url: URL) { - let encodingURL = url.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" - - if let iBoxUrl = URL(string: "iBox://url?data=" + encodingURL) { - if let tabBarController = findMainTabBarController() { - AddBookmarkManager.shared.navigateToAddBookmarkView(from: iBoxUrl, in: tabBarController) - } - } - } -} - -extension WebViewController: ErrorPageControllerDelegate { - func presentErrorPage(_ errorPage: ErrorPageViewController) { - self.present(errorPage, animated: true, completion: nil) - } - - func backButton() { - if let navController = navigationController { - navController.popViewController(animated: true) - } else { - dismiss(animated: true) - } - } -}