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 @@
-
-
-
-
-
-[](https://apps.apple.com)
-
-
-
-# πββοΈπββοΈ Introduction
-μλ
νμΈμ! 42Box iOS νμ
λλ€. π¦Β
-42Boxλ μ¬λ¬ μΉ λ§ν¬λ₯Ό μ½κ² μ μ₯νκ³ ν΄λλ³λ‘ μ 리ν μ μλ μ±μ
λλ€.Β
-λμκ΄ μλΉμ€, μΆμ
κ΄λ¦¬ λ± 42μμΈ μνμ νμμ μΈ μλΉμ€λ€μ κΈ°λ³Έ ν΄λμ λͺ¨μ μ 곡νκ³ μμ΄μ.Β
-π’Β μλΉμ€λ₯Ό μννκ² μ΄μ©νμ€ μ μλλ‘ ν΅μ¬ κΈ°λ₯μ μκ°ν©λλ€. πΒ
-
-
-### βοΈ Key Function
-1. κΈ°λ³Έ 42ν΄λ μ 곡
-
- > 42μμΈ μλΉμ€μ κ΄λ ¨λ λ§ν¬λ₯Ό μ¬μ μ μ₯ν μ μ© ν΄λλ₯Ό μ 곡ν©λλ€.
-
-2. λΆλ§ν¬ κ΄λ¦¬
-
- > μΉ λ§ν¬λ₯Ό μμ½κ² μ μ₯νκ³ λλ§μ ν΄λλ‘ μ 리ν μ μμ΅λλ€.
-
-3. μ¦κ²¨μ°ΎκΈ°
-
- > κ°μ₯ μμ£Ό λ°©λ¬Ένλ μΉ λ§ν¬μ λΉ λ₯΄κ² μ κ·ΌνκΈ° μν΄ μ¦κ²¨μ°ΎκΈ° νμΌλ‘ μΆκ°ν μ μμ΅λλ€.
-
-4. 곡μ
-
- > μΈλΆ μ±μμ μΉ λ§ν¬λ₯Ό 곡μ ν΄ λΆλ§ν¬λ₯Ό μ½κ² μΆκ°ν μ μμ΅λλ€.
-
-5. λ§ν¬ 미리보기
-
- > λ§ν¬λ₯Ό μμ ν μ΄μ§ μκ³ λ λΆλ§ν¬λ₯Ό κΈΈκ² λλ¬ λ―Έλ¦¬ λ³Ό μ μμ΅λλ€.
-
-6. μ μ€μ²λ‘ λΆλ§ν¬ μΆκ°
-
- > λΈλΌμ°μ§νλ λμ κ°λ¨ν μ μ€μ²λ‘ νμ¬ μΉ λ§ν¬λ₯Ό λΆλ§ν¬λ‘ μ½κ² μ μ₯ν μ μμ΅λλ€.
-
-7. ν
λ§ λ° μ€μ
-
- > ν
λ§μ μμ νλ©΄ μ€μ λ± κ°μΈ λ§μΆ€ν μ€μ μ ν μ μμ΅λλ€.
-
-### π· ScreenShot
-
-| ν΄λ κ΄λ¦¬ | μΈλΆ μ±μμ 곡μ | μ μ€μ²λ‘ λΆλ§ν¬ μΆκ° |
-|:---:|:---:|:---:|
-||||
-
-
-| μ¦κ²¨μ°ΎκΈ° μ€μ | ν
λ§ λ° μ€μ |
-|:---:|:---:|
-|||
-
-
-
-
-# βοΈ 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
-
-
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==-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
\ 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==-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
\ 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