├── .gitignore
├── .gitignore.swp
├── ComicKhanTests
├── CoreData
│ └── CoreDataTests.swift
├── DataService
│ ├── DataServiceTests.swift
│ └── MockCoreDataStack.swift
├── Extractor
│ ├── ExtractionDirectoriesTests.swift
│ └── ExtractorTests.swift
├── FileManager
│ └── FileManagerTests.swift
├── Info.plist
└── LibraryVCTests.swift
├── LICENSE.txt
├── Podfile
├── Podfile.lock
├── Pods
└── Target Support Files
│ ├── DirectoryWatcher
│ ├── DirectoryWatcher.debug.xcconfig
│ └── DirectoryWatcher.release.xcconfig
│ ├── UnrarKit
│ ├── UnrarKit.debug.xcconfig
│ └── UnrarKit.release.xcconfig
│ └── Zip
│ ├── Zip.debug.xcconfig
│ └── Zip.release.xcconfig
├── README.md
├── images
├── dlappstore.png
├── screen1.jpg
├── screen2.jpg
├── screen3.jpg
├── screen4.jpg
├── screen5.jpg
├── screen6.jpg
├── screen7.jpg
└── screen8.jpg
├── wutComicReader.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ ├── ComicKhanTests.xcscheme
│ │ ├── ComicKhanUITests.xcscheme
│ │ ├── ExtractorTests.xcscheme
│ │ └── wutComicReader.xcscheme
└── xcuserdata
│ └── shayan.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── wutComicReader.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── WorkspaceSettings.xcsettings
└── wutComicReader
├── AppDelegate
└── AppDelegate.swift
├── AppFont.swift
├── Assets.xcassets
├── AppIcon.appiconset
│ ├── Contents.json
│ ├── Icon.png
│ ├── icon_20pt-1.png
│ ├── icon_20pt@2x.png
│ ├── icon_20pt@3x.png
│ ├── icon_29pt.png
│ ├── icon_29pt@2x.png
│ ├── icon_29pt@3x.png
│ ├── icon_40pt-1.png
│ ├── icon_40pt@2x.png
│ ├── icon_40pt@3x.png
│ ├── icon_60pt@2x.png
│ ├── icon_60pt@3x.png
│ ├── icon_76pt.png
│ ├── icon_76pt@2x.png
│ └── icon_83.5@2x-1.png
├── Colors
│ ├── Contents.json
│ └── appBlueColor.colorset
│ │ └── Contents.json
├── Contents.json
├── launchScreen
│ ├── Contents.json
│ └── launchScreenColor.colorset
│ │ └── Contents.json
├── new icons
│ ├── Contents.json
│ ├── close.imageset
│ │ ├── Contents.json
│ │ ├── ic-actions-close-simple.png
│ │ ├── ic-actions-close-simple@2x.png
│ │ └── ic-actions-close-simple@3x.png
│ ├── doubletap-gesture.imageset
│ │ ├── Contents.json
│ │ ├── Group 2.png
│ │ ├── Group 2@2x.png
│ │ └── Group 2@3x.png
│ ├── github.imageset
│ │ ├── Artboard 1.png
│ │ ├── Artboard 1@2x.png
│ │ ├── Artboard 1@3x.png
│ │ └── Contents.json
│ ├── group2.imageset
│ │ ├── Contents.json
│ │ ├── ic-layout-picture-right.png
│ │ ├── ic-layout-picture-right@2x.png
│ │ └── ic-layout-picture-right@3x.png
│ ├── ic-actions-more-1.imageset
│ │ ├── Contents.json
│ │ ├── ic-actions-more-1.png
│ │ ├── ic-actions-more-1@2x.png
│ │ └── ic-actions-more-1@3x.png
│ ├── ic-actions-more-2.imageset
│ │ ├── Contents.json
│ │ ├── ic-actions-more-2.png
│ │ ├── ic-actions-more-2@2x.png
│ │ └── ic-actions-more-2@3x.png
│ ├── ic-actions-select.imageset
│ │ ├── Contents.json
│ │ ├── ic-actions-select.png
│ │ ├── ic-actions-select@2x.png
│ │ └── ic-actions-select@3x.png
│ ├── ic-actions-selected.imageset
│ │ ├── Contents.json
│ │ ├── ic-actions-selected.png
│ │ ├── ic-actions-selected@2x.png
│ │ └── ic-actions-selected@3x.png
│ ├── ic-actions-trash.imageset
│ │ ├── Contents.json
│ │ ├── ic-actions-trash.png
│ │ ├── ic-actions-trash@2x.png
│ │ └── ic-actions-trash@3x.png
│ ├── icon.imageset
│ │ ├── Contents.json
│ │ ├── N-Artboard d3@2x.png
│ │ └── N-Artboard l3@2x.png
│ ├── mail.imageset
│ │ ├── Artboard 1.png
│ │ ├── Artboard 1@2x.png
│ │ ├── Artboard 1@3x.png
│ │ └── Contents.json
│ ├── pinch-gesture.imageset
│ │ ├── Contents.json
│ │ ├── Group 16.png
│ │ ├── Group 16@2x.png
│ │ └── Group 16@3x.png
│ ├── setting.imageset
│ │ ├── Contents.json
│ │ ├── ic-actions-settings.png
│ │ ├── ic-actions-settings@2x.png
│ │ └── ic-actions-settings@3x.png
│ ├── single-page.imageset
│ │ ├── Contents.json
│ │ ├── ic-devices-tablet copy 2.png
│ │ ├── ic-devices-tablet copy 2@2x.png
│ │ └── ic-devices-tablet copy 2@3x.png
│ ├── smile.imageset
│ │ ├── Contents.json
│ │ ├── ic-emoji-super-smile.png
│ │ ├── ic-emoji-super-smile@2x.png
│ │ └── ic-emoji-super-smile@3x.png
│ ├── tap-gesture.imageset
│ │ ├── Contents.json
│ │ ├── Group.png
│ │ ├── Group@2x.png
│ │ └── Group@3x.png
│ └── two-pages.imageset
│ │ ├── Contents.json
│ │ ├── ic-devices-tablet copy.png
│ │ ├── ic-devices-tablet copy@2x.png
│ │ └── ic-devices-tablet copy@3x.png
├── placeholder.imageset
│ ├── Contents.json
│ └── placeholder.jpg
└── sliderThumb.imageset
│ ├── Contents.json
│ ├── Oval 2.png
│ └── Oval.png
├── Colors.swift
├── Consts.swift
├── CoreData
├── ArrayOfStringTransformer.swift
├── Comic+CoreDataClass.swift
├── Comic+CoreDataProperties.swift
├── ComicGroup+CoreDataClass.swift
├── ComicGroup+CoreDataProperties.swift
└── coredata.xcdatamodeld
│ ├── .xccurrentversion
│ ├── coredata v2.xcdatamodel
│ └── contents
│ └── coredata.xcdatamodel
│ └── contents
├── Cores
├── AppFileManager.swift
├── AppState.swift
├── ComicExteractor.swift
├── Cores.swift
├── DataService.swift
├── ExtractionDirectory.swift
├── ImageResizer.swift
└── IndexSelectionManager.swift
├── Extensions
├── +CollectionView.swift
├── +Combine.swift
├── +NSLayoutContraint.swift
├── +UIImage.swift
├── +URL.swift
├── ComicImage.swift
├── Helper.swift
├── UIKitPreview.swift
└── VCAlert.swift
├── Info.plist
├── ProgressViews
├── CircleProgressView.swift
└── RoundedProgressView.swift
├── Storyboards
└── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
└── ViewControllers
├── Book Reader
├── BookPage.swift
├── BookReader+PageVC.swift
├── BookReaderFactory.swift
├── BookReaderVC.swift
├── Reader Setting
│ ├── ReaderPageMode.swift
│ ├── ReaderSettingVC.swift
│ └── ReaderTheme.swift
├── Reader Thumbnails
│ ├── ReaderThumbnailsVC.swift
│ └── ThumbnailCell.swift
├── ReaderGuideView.swift
└── ReaderPageControllerExtensions.swift
├── DynamicConstraintViewController.swift
├── Info
└── InfoVC.swift
├── Library
├── Extensions
│ ├── Library+CollectionView.swift
│ ├── Library+DocumentBrowser.swift
│ ├── Library+EmptyViewDelegate.swift
│ ├── Library+ExtractionErrorHandeling.swift
│ ├── Library+FetchRsultHandlerDelegate.swift
│ └── Library+ProgressContainer.swift
├── LibraryFetchResultControllerHandler.swift
├── LibraryVC.swift
└── Views
│ ├── EmptyGroupView.swift
│ ├── LibraryCell.swift
│ ├── LibraryReusableView.swift
│ ├── LoadingView.swift
│ └── ProgressContainerView.swift
└── New Comic Group
└── NewGroupVC.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 | DESIGNS/
9 |
10 | ## Various settings
11 | *.pbxuser
12 | !default.pbxuser
13 | *.mode1v3
14 | !default.mode1v3
15 | *.mode2v3
16 | !default.mode2v3
17 | *.perspectivev3
18 | !default.perspectivev3
19 | xcuserdata/
20 |
21 | ## Other
22 | *.moved-aside
23 | *.xccheckout
24 | *.xcscmblueprint
25 | *.rar
26 | *.zip
27 | *.pdf
28 | *.cbz
29 | *.cbr
30 | /DESIGNS
31 |
32 | ## Obj-C/Swift specific
33 | *.hmap
34 | *.ipa
35 | *.dSYM.zip
36 | *.dSYM
37 |
38 | ## Playgrounds
39 | timeline.xctimeline
40 | playground.xcworkspace
41 |
42 | # Swift Package Manager
43 | #
44 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
45 | Packages/
46 | Package.pins
47 | Package.resolved
48 | .build/
49 |
50 | # CocoaPods
51 | #
52 | # We recommend against adding the Pods directory to your .gitignore. However
53 | # you should judge for yourself, the pros and cons are mentioned at:
54 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
55 | #
56 | Pods/
57 |
58 | # Carthage
59 | #
60 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
61 | # Carthage/Checkouts
62 |
63 | Carthage/Build
64 |
65 | fastlane
66 | #
67 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
68 | # screenshots whenever they are needed.
69 | # For more information about the recommended setup visit:
70 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
71 |
72 | fastlane/report.xml
73 | fastlane/Preview.html
74 | fastlane/screenshots/**/*.png
75 | fastlane/test_output
76 |
77 |
78 | # Mac gitignore files ----------------------------------------------
79 |
80 | # Folder view configuration files
81 | .DS_Store
82 | Desktop.ini
83 |
84 | # Thumbnail cache files
85 | ._*
86 | Thumbs.db
87 |
88 | # Files that might appear on external disks
89 | .Spotlight-V100
90 | .Trashes
91 |
92 | # Compiled Python files
93 | *.pyc
94 |
95 | # Compiled C++ files
96 | *.out
97 |
98 | # Application specific files
99 | venv
100 | node_modules
101 | .sass-cache
102 |
103 |
--------------------------------------------------------------------------------
/.gitignore.swp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/.gitignore.swp
--------------------------------------------------------------------------------
/ComicKhanTests/CoreData/CoreDataTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoreDataTests.swift
3 | // ComicKhanTests
4 | //
5 | // Created by Sha Yan on 3/6/1401 AP.
6 | // Copyright © 1401 AP wutup. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ComicKhan
11 | import CoreData
12 | class CoreDataTests: XCTestCase {
13 |
14 | var managedObjectContext: NSManagedObjectContext!
15 | override func setUpWithError() throws {
16 | try super.setUpWithError()
17 | managedObjectContext = createMockManagedContext()
18 | }
19 |
20 | override func tearDownWithError() throws {
21 | try super.tearDownWithError()
22 | managedObjectContext = nil
23 | }
24 |
25 |
26 | func testComicDescription() {
27 | let comic = Comic(context: managedObjectContext)
28 | comic.name = "Name"
29 | comic.groupName = "Froup"
30 | comic.imageNames = ["A", "B", "C"]
31 |
32 | print(comic.description)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ComicKhanTests/DataService/MockCoreDataStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockCoreDataStack.swift
3 | // ComicKhanTests
4 | //
5 | // Created by Sha Yan on 4/25/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreData
11 |
12 | func createMockManagedContext() -> NSManagedObjectContext? {
13 |
14 | //data module
15 | guard let modelURL = Bundle.main.url(forResource: "coredata", withExtension: "momd") else {
16 | XCTFail("object model not exist!")
17 | return nil
18 | }
19 | guard let objectModel = NSManagedObjectModel(contentsOf: modelURL) else {
20 | XCTFail("can't establish object model based on given url!")
21 | return nil
22 | }
23 |
24 | //cordinator
25 | let coordinator = NSPersistentStoreCoordinator(managedObjectModel: objectModel)
26 |
27 | do {
28 | try coordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
29 | }catch {
30 | XCTFail("can't load persistant store")
31 | }
32 |
33 | //nsmanagedcontext
34 | let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
35 | context.persistentStoreCoordinator = coordinator
36 |
37 | return context
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/ComicKhanTests/Extractor/ExtractionDirectoriesTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExtractionDirectoriesTests.swift
3 | // ComicKhanTests
4 | //
5 | // Created by Sha Yan on 1/24/21.
6 | // Copyright © 2021 wutup. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ComicKhan
11 |
12 | class ExtractionDirectoriesTests: XCTestCase {
13 |
14 | var extractionDir: ExtractionDirectory!
15 | let extractionDirdName = "extraction-test"
16 | let fileManager = FileManager.default
17 |
18 | override func setUpWithError() throws {
19 | let tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
20 | extractionDir = ExtractionDirectory(directoryName: extractionDirdName, baseURL: tempURL)
21 | try! extractionDir.createDirectories()
22 | }
23 |
24 | override func tearDownWithError() throws {
25 | try? fileManager.removeItem(at: extractionDir.baseURL)
26 | extractionDir = nil
27 | }
28 |
29 | func testWriteAndReadMetaData() throws {
30 | let groupName = "test-group"
31 | try! extractionDir.write(metaData: .init(groupName: groupName))
32 | let meteData = try! extractionDir.readMetaData()
33 | XCTAssertEqual(meteData.groupName, groupName)
34 |
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/ComicKhanTests/Extractor/ExtractorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // coreTests.swift
3 | // coreTests
4 | //
5 | // Created by Sha Yan on 1/26/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import UIKit
11 | import Zip
12 | import UnrarKit
13 |
14 | @testable import ComicKhan
15 |
16 | class ExtractorTests: XCTestCase {
17 |
18 | var extractor : ComicExteractor!
19 | var tempComicDirectory: URL!
20 | var tempUserDirectory: URL!
21 | let fileManager = FileManager()
22 | var testBundle: Bundle!
23 |
24 | enum Format {
25 | case cbz , cbr
26 | var string: String {
27 | switch self {
28 | case .cbr: return "cbr"
29 | case .cbz: return "cbz"
30 | }
31 | }
32 | }
33 |
34 | struct ComicInfo {
35 | let name: String
36 | let pageCount : Int
37 | let format: Format
38 | var pathComponent: String { name + "." + format.string }
39 | }
40 |
41 | struct ComicDirectory {
42 | let name: String
43 | let comicsInfo: [ComicInfo]
44 | }
45 |
46 |
47 |
48 | //MARK:- inital functions
49 |
50 | override func setUp() {
51 | super.setUp()
52 | creatTempDiractories()
53 | extractor = ComicExteractor(userDirectory: tempUserDirectory, comicDirectory: tempComicDirectory)
54 | testBundle = Bundle(for: type(of: self))
55 |
56 | }
57 |
58 | func creatTempDiractories(){
59 | let tempFromURL = URL(fileURLWithPath: NSTemporaryDirectory() + "from")
60 | let tempToURL = URL(fileURLWithPath: NSTemporaryDirectory() + "to")
61 | try! fileManager.createDirectory(at: tempFromURL, withIntermediateDirectories: true, attributes: nil)
62 | try! fileManager.createDirectory(at: tempToURL, withIntermediateDirectories: true, attributes: nil)
63 | tempUserDirectory = tempFromURL
64 | tempComicDirectory = tempToURL
65 | }
66 |
67 | override func tearDown() {
68 | try! fileManager.removeItem(at: tempUserDirectory)
69 | try! fileManager.removeItem(at: tempComicDirectory)
70 | extractor = nil
71 | tempComicDirectory = nil
72 | tempUserDirectory = nil
73 | super.tearDown()
74 | }
75 |
76 |
77 |
78 |
79 | //MARK:- test functions
80 |
81 | //test initial values
82 |
83 | func testCashDirectoriesDoExist() {
84 | XCTAssertEqual(tempUserDirectory.path, NSTemporaryDirectory() + "from")
85 | XCTAssertEqual(tempComicDirectory.path, NSTemporaryDirectory() + "to")
86 | }
87 |
88 | //extractor search through "sutAppfileManager.userDiractory" and extact all the comic files to "sutAppfileManager.comicDiractory" and check files extracted completely or not.
89 | func testUserComicsDoExtractInComicDiractory() throws{
90 | //given
91 |
92 | let comicInfo = ComicInfo(name: "1-fish",
93 | pageCount: 1,
94 | format: .cbz)
95 |
96 |
97 | let comicFilePath = testBundle.path(forResource: comicInfo.name, ofType: comicInfo.format.string)
98 |
99 | XCTAssertNotNil(comicFilePath)
100 |
101 | //copy test comic to temp folder
102 | try! fileManager.copyItem(at: URL(fileURLWithPath: comicFilePath!) , to: tempUserDirectory.appendingPathComponent(comicInfo.name + "." + comicInfo.format.string))
103 |
104 | let coppiedComicPath = tempUserDirectory.appendingPathComponent(comicInfo.name + "." + comicInfo.format.string).path
105 | let didComicCoppied = fileManager.fileExists(atPath: coppiedComicPath)
106 |
107 | XCTAssertTrue(didComicCoppied)
108 |
109 | //when
110 |
111 | extractor.extractUserComicsIntoComicDiractory()
112 |
113 | let extractionURL = tempComicDirectory.appendingPathComponent(comicInfo.name)
114 | let extractedExist = fileManager.fileExists(atPath: extractionURL.path)
115 |
116 | //then
117 |
118 |
119 | XCTAssertTrue(extractedExist, "File extracted?")
120 |
121 | let extractionDirectory = ExtractionDirectory(directoryName: extractionURL.fileName(),
122 | baseURL: tempComicDirectory)
123 | let extractedFilesCount =
124 | try fileManager.contentsOfDirectory(
125 | at: extractionDirectory.originalImagesDirectoryURL,
126 | includingPropertiesForKeys: nil,
127 | options: .skipsHiddenFiles)
128 | .filter { validImageFormats.contains($0.pathExtension.lowercased()) }
129 | .count
130 |
131 |
132 | XCTAssertEqual(extractedFilesCount, comicInfo.pageCount)
133 |
134 | }
135 |
136 | func testNumberOfComicsAboutToExtract() {
137 | let infos: [ComicInfo] = [
138 | .init(name: "1-fish", pageCount: 1, format: .cbz),
139 | .init(name: "2-dogs", pageCount: 2, format: .cbz),
140 | .init(name: "3-fish", pageCount: 3, format: .cbz),
141 | .init(name: "4-dogs", pageCount: 4, format: .cbz)
142 | ]
143 |
144 | let comicDirectories: [ComicDirectory] = [
145 | .init(name: "1", comicsInfo: [infos[0], infos[1]]),
146 | .init(name: "2", comicsInfo: [infos[2], infos[3]])
147 | ]
148 |
149 |
150 |
151 | for directory in comicDirectories {
152 | //create directory first
153 | let dirURL = tempUserDirectory.appendingPathComponent(directory.name)
154 | try! fileManager.createDirectory(atPath: dirURL.path, withIntermediateDirectories: true, attributes: nil)
155 |
156 | //move its comics
157 | for info in directory.comicsInfo {
158 | let url = testBundle.url(forResource: info.name, withExtension: info.format.string)!
159 | try! fileManager.copyItem(at: url, to: dirURL.appendingPathComponent(info.pathComponent))
160 | }
161 | }
162 |
163 | extractor.extractUserComicsIntoComicDiractory()
164 |
165 | //check if total numbers match
166 | XCTAssertEqual(extractor.totalComicCount, 4)
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/ComicKhanTests/FileManager/FileManagerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileManagerTests.swift
3 | // ComicKhanTests
4 | //
5 | // Created by Sha Yan on 4/25/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class FileManagerTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | // This is an example of a functional test case.
23 | // Use XCTAssert and related functions to verify your tests produce the correct results.
24 | }
25 |
26 | func testPerformanceExample() throws {
27 | // This is an example of a performance test case.
28 | self.measure {
29 | // Put the code you want to measure the time of here.
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/ComicKhanTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ComicKhanTests/LibraryVCTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LibraryVCTests.swift
3 | // ComicKhanTests
4 | //
5 | // Created by Sha Yan on 4/26/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import UIKit
11 | import CoreData
12 |
13 | @testable import ComicKhan
14 |
15 | //class LibraryVCTests: XCTestCase {
16 | //
17 | // var libraryVC: LibraryVC!
18 | // var mockContext: NSManagedObjectContext!
19 | //
20 | // override func setUpWithError() throws {
21 | // libraryVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LibraryVC") as! LibraryVC
22 | // libraryVC.loadView()
23 | //
24 | // mockContext = createMockManagedContext()!
25 | // libraryVC.setupForTes(dataService: DataService(managedContext: mockContext))
26 | //
27 | // }
28 | //
29 | // override func tearDownWithError() throws {
30 | // libraryVC = nil
31 | // mockContext = nil
32 | // newComicsGroup = nil
33 | // }
34 | //
35 | // func testDataService() {
36 | // XCTAssertNotNil(libraryVC.dataService)
37 | // XCTAssertNotNil(libraryVC.fetchResultController)
38 | // XCTAssertEqual(libraryVC.fetchResultController.managedObjectContext, mockContext)
39 | // print(libraryVC.fetchResultController.sections?.first?.name)
40 | //// XCTAssertEqual(libraryVC.fetchResultController.sections?.first?.name, newComicsGroup.name)
41 | // }
42 | //
43 | // func testCollectionViewInsertation() {
44 | //
45 | // let newComic1 = Comic(context: mockContext)
46 | // newComic1.name = "Batman"
47 | // newComic1.imageNames = ["01" , "02" , "03"]
48 | // newComic1.thumbnailNames = ["01" , "02" , "03"]
49 | // newComic1.lastVisitedPage = 0
50 | // newComic1.id = UUID()
51 | // newComic1.ofComicGroup = newComicsGroup
52 | //
53 | // let newComic2 = Comic(context: mockContext)
54 | // newComic2.name = "Superman"
55 | // newComic2.imageNames = ["01" , "02" , "03"]
56 | // newComic2.thumbnailNames = ["01" , "02" , "03"]
57 | // newComic2.lastVisitedPage = 0
58 | // newComic2.id = UUID()
59 | // newComic2.ofComicGroup = newComicsGroup
60 | //
61 | // try! mockContext.save()
62 | //
63 | //
64 | // try! libraryVC.fetchResultController.performFetch()
65 | // print(libraryVC.fetchResultController.fetchedObjects)
66 | // XCTAssertEqual(libraryVC.fetchResultController.fetchedObjects?.count, 2)
67 | // XCTAssertEqual(libraryVC.fetchResultController.sections?.count, 1)
68 | // XCTAssertEqual(libraryVC.bookCollectionView.numberOfItems(inSection: 0), 2)
69 | // libraryVC.bookCollectionView.reloadData()
70 | //// let cell = libraryVC.bookCollectionView.cellForItem(at: IndexPath(row: 0, section: 0))
71 | //// XCTAssertNotNil(cell)
72 | //
73 | //
74 | // }
75 | //
76 | //}
77 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'wutComicReader' do
5 | # use_modular_headers!
6 | use_frameworks!
7 |
8 | pod "UnrarKit"
9 | pod 'Zip', '~> 1.1'
10 | pod 'DirectoryWatcher'
11 |
12 | target 'ComicKhanTests' do
13 | inherit! :search_paths
14 |
15 | pod 'Zip', '~> 1.1'
16 | pod "UnrarKit"
17 |
18 | end
19 |
20 | end
21 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - DirectoryWatcher (2.4.0)
3 | - UnrarKit (2.9):
4 | - UnrarKit/unrar-lib (= 2.9)
5 | - UnrarKit/unrar-lib (2.9)
6 | - Zip (1.1.0)
7 |
8 | DEPENDENCIES:
9 | - DirectoryWatcher
10 | - UnrarKit
11 | - Zip (~> 1.1)
12 |
13 | SPEC REPOS:
14 | trunk:
15 | - DirectoryWatcher
16 | - UnrarKit
17 | - Zip
18 |
19 | SPEC CHECKSUMS:
20 | DirectoryWatcher: a1c1d838025186d3cfaa9cfc6eee4064093dcec1
21 | UnrarKit: 99e3f0222a98a212188e1a6975dcf5aa798d26dd
22 | Zip: 8877eede3dda76bcac281225c20e71c25270774c
23 |
24 | PODFILE CHECKSUM: b83217336c232a84b74d52c95274e09fa75c05f7
25 |
26 | COCOAPODS: 1.15.2
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/DirectoryWatcher/DirectoryWatcher.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/DirectoryWatcher
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
6 | PODS_BUILD_DIR = ${BUILD_DIR}
7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
9 | PODS_ROOT = ${SRCROOT}
10 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/DirectoryWatcher
11 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
12 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
13 | SKIP_INSTALL = YES
14 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
15 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/DirectoryWatcher/DirectoryWatcher.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/DirectoryWatcher
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
6 | PODS_BUILD_DIR = ${BUILD_DIR}
7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
9 | PODS_ROOT = ${SRCROOT}
10 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/DirectoryWatcher
11 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
12 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
13 | SKIP_INSTALL = YES
14 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
15 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/UnrarKit/UnrarKit.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/UnrarKit
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | OTHER_CFLAGS = $(inherited) -Wno-return-type -Wno-logical-op-parentheses -Wno-conversion -Wno-parentheses -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-unused-command-line-argument -Wno-strict-prototypes -Wno-conditional-uninitialized
5 | OTHER_CPLUSPLUSFLAGS = $(inherited) -DSILENT -DRARDLL $(OTHER_CFLAGS)
6 | OTHER_LDFLAGS = $(inherited) -l"c++" -l"z"
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
10 | PODS_ROOT = ${SRCROOT}
11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/UnrarKit
12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
14 | SKIP_INSTALL = YES
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/UnrarKit/UnrarKit.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/UnrarKit
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | OTHER_CFLAGS = $(inherited) -Wno-return-type -Wno-logical-op-parentheses -Wno-conversion -Wno-parentheses -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-unused-command-line-argument -Wno-strict-prototypes -Wno-conditional-uninitialized
5 | OTHER_CPLUSPLUSFLAGS = $(inherited) -DSILENT -DRARDLL $(OTHER_CFLAGS)
6 | OTHER_LDFLAGS = $(inherited) -l"c++" -l"z"
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
10 | PODS_ROOT = ${SRCROOT}
11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/UnrarKit
12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
14 | SKIP_INSTALL = YES
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Zip/Zip.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Zip
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SRCROOT)/Zip/Zip/
5 | OTHER_LDFLAGS = $(inherited) -l"z"
6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
10 | PODS_ROOT = ${SRCROOT}
11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Zip
12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
14 | SKIP_INSTALL = YES
15 | SWIFT_INCLUDE_PATHS = $(inherited) $(SRCROOT)/Zip/Zip/minizip/**
16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Zip/Zip.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Zip
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SRCROOT)/Zip/Zip/
5 | OTHER_LDFLAGS = $(inherited) -l"z"
6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
10 | PODS_ROOT = ${SRCROOT}
11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Zip
12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
14 | SKIP_INSTALL = YES
15 | SWIFT_INCLUDE_PATHS = $(inherited) $(SRCROOT)/Zip/Zip/minizip/**
16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # ComicKhan - CBZ/CBR/PDF Reader
3 |
4 | *Notice:
5 | Unfortunately, during the past few years a lot has happened in my life and I couldn't maintain this repo and project properly. At the same time the codebase has become a bit old and I believe any possible feature development or bug fixing is really difficult right now. I decided to archive this repo.
6 | But regardless of that, I am still willing to work on this project. In the feature, if the life becomes easier and I have the time, I am definitely looking forward to planning a rewrite using newer frameworks like swiftUI and async await and probably try to reuse bunch of components from here.*
7 |
8 | ComicKhan is an app for reading your comic files (.cbz /.cbr/.pdf) on your iPhone and iPad.
9 |
10 | ## Screenshots
11 |
12 |
13 |
14 |  |
15 |  |
16 |  |
17 |
18 |
19 |
20 |  |
21 |  |
22 |  |
23 |
24 |
25 |
26 |
27 |
28 |  |
29 |  |
30 |
31 |
32 |
33 |
34 |
35 | ## Features
36 | * Detects new comic files when you add them into the document directory and starts extracting them while showing their extraction progress.
37 | * Allows you to reading your comics in double pages or single page based.
38 | * Stores the last page you have read and displays its reading progress on each book in your library.
39 | * Resizes comic images for smooth scrolling and better performance.
40 | * Allows you to group your comics and organize them in the library.
41 |
42 | ## TODO
43 | - [x] Support for comics with PDF format
44 | - [x] Support for extracting directories of comics.
45 | - [X] Switch between dark mode / light mode in book reader without needing to change the system color preferences.
46 | - [ ] Ability to sort comic groups based on name, date added, etc.
47 |
48 |
49 |
50 | ## Credits
51 | * Thanks to [GianniCarlo](https://github.com/GianniCarlo/DirectoryWatcher) for directory watcher.
52 | * Thanks to [abbeycode](https://github.com/abbeycode/UnrarKit) and [marmelroy](https://github.com/marmelroy/Zip) for compressed files extraction.
53 | * Thanks to [BilekUI](https://twitter.com/BilekUI) for icons and [icone8](https://icons8.com) for illustrations.
54 |
--------------------------------------------------------------------------------
/images/dlappstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/images/dlappstore.png
--------------------------------------------------------------------------------
/images/screen1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/images/screen1.jpg
--------------------------------------------------------------------------------
/images/screen2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/images/screen2.jpg
--------------------------------------------------------------------------------
/images/screen3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/images/screen3.jpg
--------------------------------------------------------------------------------
/images/screen4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/images/screen4.jpg
--------------------------------------------------------------------------------
/images/screen5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/images/screen5.jpg
--------------------------------------------------------------------------------
/images/screen6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/images/screen6.jpg
--------------------------------------------------------------------------------
/images/screen7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/images/screen7.jpg
--------------------------------------------------------------------------------
/images/screen8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/images/screen8.jpg
--------------------------------------------------------------------------------
/wutComicReader.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/wutComicReader.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/wutComicReader.xcodeproj/xcshareddata/xcschemes/ComicKhanTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
27 |
28 |
29 |
30 |
31 |
32 |
42 |
43 |
49 |
50 |
52 |
53 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/wutComicReader.xcodeproj/xcshareddata/xcschemes/ComicKhanUITests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
44 |
45 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/wutComicReader.xcodeproj/xcshareddata/xcschemes/ExtractorTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
27 |
28 |
29 |
30 |
31 |
32 |
42 |
43 |
49 |
50 |
52 |
53 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/wutComicReader.xcodeproj/xcshareddata/xcschemes/wutComicReader.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/wutComicReader.xcodeproj/xcuserdata/shayan.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | ComicKhanTests.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 1
11 |
12 | ComicKhanUITests.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 8
16 |
17 | ExtractorTests.xcscheme_^#shared#^_
18 |
19 | orderHint
20 | 7
21 |
22 | wutComicReader.xcscheme_^#shared#^_
23 |
24 | orderHint
25 | 0
26 |
27 |
28 | SuppressBuildableAutocreation
29 |
30 | E40C7A9822A1168C00D7CD17
31 |
32 | primary
33 |
34 |
35 | E42B9EA424506E58007B3803
36 |
37 | primary
38 |
39 |
40 | E42B9EB8245071DA007B3803
41 |
42 | primary
43 |
44 |
45 | E42B9EC624507215007B3803
46 |
47 | primary
48 |
49 |
50 | E42B9ED424507275007B3803
51 |
52 | primary
53 |
54 |
55 | E42B9EE7245075F6007B3803
56 |
57 | primary
58 |
59 |
60 | E42B9EFC245077CF007B3803
61 |
62 | primary
63 |
64 |
65 | E42B9F2924507A3A007B3803
66 |
67 | primary
68 |
69 |
70 | E475D0AA245751D10005CF8F
71 |
72 | primary
73 |
74 |
75 | E4DA532124507DB800A244FD
76 |
77 | primary
78 |
79 |
80 | E4EBEDCB23DD7C49000B317D
81 |
82 | primary
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/wutComicReader.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/wutComicReader.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/wutComicReader.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/wutComicReader/AppDelegate/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // wutComicReader
4 | //
5 | // Created by Shayan on 5/31/19.
6 | // Copyright © 2019 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreData
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 |
17 |
18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
19 |
20 | if AppState.main.didAppLaunchedForFirstTime() {
21 | do {
22 | try Cores.main.dataService.createGroupForNewComics()
23 | try Cores.main.appfileManager.makeAppDirectory()
24 | AppState.main.setAppDidLaunchedForFirstTime()
25 |
26 | }catch let error {
27 | fatalError("Initial setup was failed: " + error.localizedDescription)
28 | }
29 | }
30 |
31 | print("🏠 \(NSHomeDirectory())")
32 | return true
33 | }
34 |
35 | func applicationWillResignActive(_ application: UIApplication) {
36 | if let navigationController = self.window?.rootViewController as? UINavigationController {
37 | if let bookReaderVC = navigationController.visibleViewController as? BookReaderVC,
38 | let page = bookReaderVC.lastViewedPage,
39 | let comic = bookReaderVC.comic{
40 | try? Cores.main.dataService.saveLastPageOf(comic: comic, lastPage: page)
41 | }
42 | }
43 | }
44 |
45 | func applicationDidEnterBackground(_ application: UIApplication) {
46 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
47 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
48 | }
49 |
50 | func applicationWillEnterForeground(_ application: UIApplication) {
51 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
52 |
53 | }
54 |
55 | func applicationDidBecomeActive(_ application: UIApplication) {
56 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
57 | }
58 |
59 | func applicationWillTerminate(_ application: UIApplication) {
60 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
61 | let _ = diractoryWatcher?.stopWatching()
62 | print("terminated")
63 | }
64 |
65 | func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
66 | // let bookReaderVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "bookReader")
67 |
68 | if let navigationController = self.window?.rootViewController as? UINavigationController {
69 | if navigationController.visibleViewController is BookReaderVC ||
70 | navigationController.visibleViewController is LibraryVC {
71 | return .allButUpsideDown
72 | }else{
73 | return .portrait
74 | }
75 | }else{
76 | return .portrait
77 | }
78 | }
79 |
80 |
81 | }
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/wutComicReader/AppFont.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppFont.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 3/2/1401 AP.
6 | // Copyright © 1401 AP wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | protocol AppFont {
11 | var body: UIFont { get }
12 | var caption: UIFont { get }
13 | var caption2: UIFont { get }
14 | var h1: UIFont { get }
15 | var h2: UIFont { get }
16 |
17 | }
18 |
19 | struct SystemFont: AppFont {
20 | let body: UIFont = UIFont.preferredFont(forTextStyle: .body)
21 |
22 | let caption: UIFont = UIFont.preferredFont(forTextStyle: .caption1)
23 |
24 | let h1: UIFont = UIFont.preferredFont(forTextStyle: .title1)
25 |
26 | let h2: UIFont = UIFont.preferredFont(forTextStyle: .title2)
27 |
28 | let caption2: UIFont = UIFont.preferredFont(forTextStyle: .caption2)
29 | }
30 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_20pt@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "icon_20pt@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "icon_29pt.png",
17 | "idiom" : "iphone",
18 | "scale" : "1x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "icon_29pt@2x.png",
23 | "idiom" : "iphone",
24 | "scale" : "2x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "icon_29pt@3x.png",
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "filename" : "icon_40pt@2x.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "icon_40pt@3x.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "40x40"
44 | },
45 | {
46 | "filename" : "icon_60pt@2x.png",
47 | "idiom" : "iphone",
48 | "scale" : "2x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "icon_60pt@3x.png",
53 | "idiom" : "iphone",
54 | "scale" : "3x",
55 | "size" : "60x60"
56 | },
57 | {
58 | "filename" : "icon_20pt-1.png",
59 | "idiom" : "ipad",
60 | "scale" : "1x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "icon_40pt-1.png",
65 | "idiom" : "ipad",
66 | "scale" : "2x",
67 | "size" : "20x20"
68 | },
69 | {
70 | "idiom" : "ipad",
71 | "scale" : "1x",
72 | "size" : "29x29"
73 | },
74 | {
75 | "idiom" : "ipad",
76 | "scale" : "2x",
77 | "size" : "29x29"
78 | },
79 | {
80 | "idiom" : "ipad",
81 | "scale" : "1x",
82 | "size" : "40x40"
83 | },
84 | {
85 | "idiom" : "ipad",
86 | "scale" : "2x",
87 | "size" : "40x40"
88 | },
89 | {
90 | "filename" : "icon_76pt.png",
91 | "idiom" : "ipad",
92 | "scale" : "1x",
93 | "size" : "76x76"
94 | },
95 | {
96 | "filename" : "icon_76pt@2x.png",
97 | "idiom" : "ipad",
98 | "scale" : "2x",
99 | "size" : "76x76"
100 | },
101 | {
102 | "filename" : "icon_83.5@2x-1.png",
103 | "idiom" : "ipad",
104 | "scale" : "2x",
105 | "size" : "83.5x83.5"
106 | },
107 | {
108 | "filename" : "Icon.png",
109 | "idiom" : "ios-marketing",
110 | "scale" : "1x",
111 | "size" : "1024x1024"
112 | }
113 | ],
114 | "info" : {
115 | "author" : "xcode",
116 | "version" : 1
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_20pt-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_20pt-1.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_29pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_29pt.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_40pt-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_40pt-1.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_76pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_76pt.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x-1.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/Colors/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/Colors/appBlueColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "0.125",
13 | "alpha" : "1.000",
14 | "blue" : "0.412",
15 | "green" : "0.286"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0.392",
31 | "alpha" : "1.000",
32 | "blue" : "0.710",
33 | "green" : "0.569"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/launchScreen/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/launchScreen/launchScreenColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.898",
9 | "green" : "0.898",
10 | "red" : "0.898"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.157",
27 | "green" : "0.157",
28 | "red" : "0.157"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/close.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic-actions-close-simple.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "ic-actions-close-simple@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "ic-actions-close-simple@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/close.imageset/ic-actions-close-simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/close.imageset/ic-actions-close-simple.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/close.imageset/ic-actions-close-simple@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/close.imageset/ic-actions-close-simple@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/close.imageset/ic-actions-close-simple@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/close.imageset/ic-actions-close-simple@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/doubletap-gesture.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 2.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Group 2@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Group 2@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/doubletap-gesture.imageset/Group 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/doubletap-gesture.imageset/Group 2.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/doubletap-gesture.imageset/Group 2@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/doubletap-gesture.imageset/Group 2@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/doubletap-gesture.imageset/Group 2@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/doubletap-gesture.imageset/Group 2@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/github.imageset/Artboard 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/github.imageset/Artboard 1.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/github.imageset/Artboard 1@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/github.imageset/Artboard 1@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/github.imageset/Artboard 1@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/github.imageset/Artboard 1@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/github.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Artboard 1.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Artboard 1@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Artboard 1@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/group2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic-layout-picture-right.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "ic-layout-picture-right@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "ic-layout-picture-right@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/group2.imageset/ic-layout-picture-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/group2.imageset/ic-layout-picture-right.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/group2.imageset/ic-layout-picture-right@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/group2.imageset/ic-layout-picture-right@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/group2.imageset/ic-layout-picture-right@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/group2.imageset/ic-layout-picture-right@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-more-1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic-actions-more-1.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "ic-actions-more-1@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "ic-actions-more-1@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-more-1.imageset/ic-actions-more-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-more-1.imageset/ic-actions-more-1.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-more-1.imageset/ic-actions-more-1@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-more-1.imageset/ic-actions-more-1@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-more-1.imageset/ic-actions-more-1@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-more-1.imageset/ic-actions-more-1@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-more-2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic-actions-more-2.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "ic-actions-more-2@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "ic-actions-more-2@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-more-2.imageset/ic-actions-more-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-more-2.imageset/ic-actions-more-2.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-more-2.imageset/ic-actions-more-2@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-more-2.imageset/ic-actions-more-2@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-more-2.imageset/ic-actions-more-2@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-more-2.imageset/ic-actions-more-2@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-select.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic-actions-select.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "ic-actions-select@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "ic-actions-select@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-select.imageset/ic-actions-select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-select.imageset/ic-actions-select.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-select.imageset/ic-actions-select@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-select.imageset/ic-actions-select@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-select.imageset/ic-actions-select@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-select.imageset/ic-actions-select@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-selected.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic-actions-selected.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "ic-actions-selected@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "ic-actions-selected@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-selected.imageset/ic-actions-selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-selected.imageset/ic-actions-selected.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-selected.imageset/ic-actions-selected@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-selected.imageset/ic-actions-selected@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-selected.imageset/ic-actions-selected@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-selected.imageset/ic-actions-selected@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-trash.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic-actions-trash.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "ic-actions-trash@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "ic-actions-trash@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-trash.imageset/ic-actions-trash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-trash.imageset/ic-actions-trash.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-trash.imageset/ic-actions-trash@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-trash.imageset/ic-actions-trash@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/ic-actions-trash.imageset/ic-actions-trash@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/ic-actions-trash.imageset/ic-actions-trash@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "appearances" : [
9 | {
10 | "appearance" : "luminosity",
11 | "value" : "dark"
12 | }
13 | ],
14 | "idiom" : "universal",
15 | "scale" : "1x"
16 | },
17 | {
18 | "filename" : "N-Artboard l3@2x.png",
19 | "idiom" : "universal",
20 | "scale" : "2x"
21 | },
22 | {
23 | "appearances" : [
24 | {
25 | "appearance" : "luminosity",
26 | "value" : "dark"
27 | }
28 | ],
29 | "filename" : "N-Artboard d3@2x.png",
30 | "idiom" : "universal",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "universal",
35 | "scale" : "3x"
36 | },
37 | {
38 | "appearances" : [
39 | {
40 | "appearance" : "luminosity",
41 | "value" : "dark"
42 | }
43 | ],
44 | "idiom" : "universal",
45 | "scale" : "3x"
46 | }
47 | ],
48 | "info" : {
49 | "author" : "xcode",
50 | "version" : 1
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/icon.imageset/N-Artboard d3@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/icon.imageset/N-Artboard d3@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/icon.imageset/N-Artboard l3@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/icon.imageset/N-Artboard l3@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/mail.imageset/Artboard 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/mail.imageset/Artboard 1.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/mail.imageset/Artboard 1@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/mail.imageset/Artboard 1@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/mail.imageset/Artboard 1@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/mail.imageset/Artboard 1@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/mail.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Artboard 1.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Artboard 1@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Artboard 1@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/pinch-gesture.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 16.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Group 16@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Group 16@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/pinch-gesture.imageset/Group 16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/pinch-gesture.imageset/Group 16.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/pinch-gesture.imageset/Group 16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/pinch-gesture.imageset/Group 16@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/pinch-gesture.imageset/Group 16@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/pinch-gesture.imageset/Group 16@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/setting.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic-actions-settings.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "ic-actions-settings@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "ic-actions-settings@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/setting.imageset/ic-actions-settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/setting.imageset/ic-actions-settings.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/setting.imageset/ic-actions-settings@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/setting.imageset/ic-actions-settings@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/setting.imageset/ic-actions-settings@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/setting.imageset/ic-actions-settings@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/single-page.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic-devices-tablet copy 2.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "ic-devices-tablet copy 2@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "ic-devices-tablet copy 2@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/single-page.imageset/ic-devices-tablet copy 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/single-page.imageset/ic-devices-tablet copy 2.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/single-page.imageset/ic-devices-tablet copy 2@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/single-page.imageset/ic-devices-tablet copy 2@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/single-page.imageset/ic-devices-tablet copy 2@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/single-page.imageset/ic-devices-tablet copy 2@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/smile.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic-emoji-super-smile.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "ic-emoji-super-smile@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "ic-emoji-super-smile@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/smile.imageset/ic-emoji-super-smile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/smile.imageset/ic-emoji-super-smile.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/smile.imageset/ic-emoji-super-smile@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/smile.imageset/ic-emoji-super-smile@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/smile.imageset/ic-emoji-super-smile@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/smile.imageset/ic-emoji-super-smile@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/tap-gesture.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Group@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Group@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/tap-gesture.imageset/Group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/tap-gesture.imageset/Group.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/tap-gesture.imageset/Group@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/tap-gesture.imageset/Group@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/tap-gesture.imageset/Group@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/tap-gesture.imageset/Group@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/two-pages.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ic-devices-tablet copy.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "ic-devices-tablet copy@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "ic-devices-tablet copy@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/two-pages.imageset/ic-devices-tablet copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/two-pages.imageset/ic-devices-tablet copy.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/two-pages.imageset/ic-devices-tablet copy@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/two-pages.imageset/ic-devices-tablet copy@2x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/new icons/two-pages.imageset/ic-devices-tablet copy@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/new icons/two-pages.imageset/ic-devices-tablet copy@3x.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/placeholder.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "placeholder.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/placeholder.imageset/placeholder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/placeholder.imageset/placeholder.jpg
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/sliderThumb.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "1x",
10 | "appearances" : [
11 | {
12 | "appearance" : "luminosity",
13 | "value" : "dark"
14 | }
15 | ]
16 | },
17 | {
18 | "idiom" : "universal",
19 | "filename" : "Oval.png",
20 | "scale" : "2x"
21 | },
22 | {
23 | "idiom" : "universal",
24 | "filename" : "Oval 2.png",
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "universal",
35 | "scale" : "3x"
36 | },
37 | {
38 | "idiom" : "universal",
39 | "scale" : "3x",
40 | "appearances" : [
41 | {
42 | "appearance" : "luminosity",
43 | "value" : "dark"
44 | }
45 | ]
46 | }
47 | ],
48 | "info" : {
49 | "version" : 1,
50 | "author" : "xcode"
51 | }
52 | }
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/sliderThumb.imageset/Oval 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/sliderThumb.imageset/Oval 2.png
--------------------------------------------------------------------------------
/wutComicReader/Assets.xcassets/sliderThumb.imageset/Oval.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaysugg/ComicKhan/1dd9070a44fec8bb03c4587ce17e8321eada7998/wutComicReader/Assets.xcassets/sliderThumb.imageset/Oval.png
--------------------------------------------------------------------------------
/wutComicReader/Colors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Colors.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 11/26/19.
6 | // Copyright © 2019 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIColor {
13 | static var appMainLabelColor : UIColor {
14 | return .label
15 | }
16 |
17 | static var appSeconedlabelColor : UIColor {
18 | return .secondaryLabel
19 | }
20 |
21 | static var appBackground : UIColor = {
22 | return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in
23 | if UITraitCollection.userInterfaceStyle == .dark {
24 | return .secondarySystemBackground
25 | }else{
26 | return .systemBackground
27 | }
28 | }
29 | }()
30 |
31 | static var appSecondaryBackground : UIColor {
32 | return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in
33 | if UITraitCollection.userInterfaceStyle == .dark {
34 | return .systemBackground
35 | }else{
36 | return .secondarySystemBackground
37 | }
38 | }
39 |
40 | }
41 |
42 | static var appSecondaryLabel : UIColor {
43 | return .secondaryLabel
44 | }
45 |
46 | static var appProgressColor : UIColor {
47 | return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in
48 | if UITraitCollection.userInterfaceStyle == .dark {
49 | return #colorLiteral(red: 0.03297975659, green: 0.1976064146, blue: 0.2944883406, alpha: 1)
50 | }else{
51 | return #colorLiteral(red: 0.4221869409, green: 0.6506024003, blue: 0.8026198745, alpha: 1)
52 | }
53 | }
54 | }
55 |
56 |
57 | static var appMainSecondary : UIColor {
58 | return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in
59 | if UITraitCollection.userInterfaceStyle == .dark {
60 | return #colorLiteral(red: 0.2186438143, green: 0.4104945064, blue: 0.5327916145, alpha: 1)
61 | }else{
62 | return #colorLiteral(red: 0.03621871024, green: 0.1935892105, blue: 0.2905158401, alpha: 1)
63 | }
64 | }
65 | }
66 |
67 | static let appMainColor = UIColor(named: "appBlueColor")!
68 | }
69 |
--------------------------------------------------------------------------------
/wutComicReader/Consts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Consts.swift
3 | // wutComicReader
4 | //
5 | // Created by Shayan on 6/8/19.
6 | // Copyright © 2019 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 |
13 | //var appDirectory : URL?
14 |
15 |
16 | let validComicFormats = ["pdf", "cbz", "cbr"]
17 | let validImageFormats = ["jpeg", "jpg", "png"]
18 |
19 | enum CollectioViewIDs {
20 | case comicCell
21 | case comicGroupHeader
22 |
23 | var id: String {
24 | switch self {
25 | case .comicCell: return "comic-cell"
26 | case .comicGroupHeader: return "comic-group-header"
27 | }
28 | }
29 | }
30 |
31 | var previouseOriantation: UIDeviceOrientation? {
32 | didSet{
33 | if previouseOriantation!.isLandscape {
34 | print("landscaped")
35 | }else if previouseOriantation!.isPortrait {
36 | print("portriat")
37 | }else{
38 | print("flat")
39 | }
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/wutComicReader/CoreData/ArrayOfStringTransformer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayOfStringTransformer.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 1/5/21.
6 | // Copyright © 2021 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | @objc(ArrayOfStringsTransformer)
12 | public final class ArrayOfStringsTransformer: ValueTransformer {
13 |
14 | public override func transformedValue(_ value: Any?) -> Any? {
15 |
16 | guard let strings = value as? [String] else { return nil }
17 |
18 | do {
19 | let data = try NSKeyedArchiver.archivedData(withRootObject: strings, requiringSecureCoding: true)
20 | return data
21 | }catch {
22 | assertionFailure("Failed to transform [String] to Data")
23 | return nil
24 | }
25 | }
26 |
27 | public override func reverseTransformedValue(_ value: Any?) -> Any? {
28 | guard let data = value as? Data else { return nil }
29 |
30 | do {
31 | let unarchivedStrings = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSArray.self, from: data)
32 | return unarchivedStrings
33 | }catch {
34 | assertionFailure("Failed to transform Date to [String]")
35 | return nil
36 | }
37 | }
38 |
39 | override public class func transformedValueClass() -> AnyClass {
40 | return NSArray.self
41 | }
42 |
43 | override public class func allowsReverseTransformation() -> Bool {
44 | return true
45 | }
46 | }
47 |
48 |
49 | extension ArrayOfStringsTransformer {
50 | static let name = NSValueTransformerName(String(describing: ArrayOfStringsTransformer.self))
51 |
52 | public static func register() {
53 | let transformer = ArrayOfStringsTransformer()
54 | ValueTransformer.setValueTransformer(transformer, forName: name)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/wutComicReader/CoreData/Comic+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Comic+CoreDataClass.swift
3 | //
4 | //
5 | // Created by Sha Yan on 1/27/20.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | public class Comic: NSManagedObject {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/wutComicReader/CoreData/Comic+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Comic+CoreDataProperties.swift
3 | //
4 | //
5 | // Created by Sha Yan on 3/11/20.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension Comic {
14 | @NSManaged public var id: UUID
15 | @NSManaged public var imageNames: [String]
16 | @NSManaged public var lastVisitedPage: Int16
17 | @NSManaged public var name: String
18 | @NSManaged public var thumbnailNames: [String]
19 | @NSManaged public var ofComicGroup: ComicGroup?
20 | @NSManaged public var groupName: String
21 |
22 | static var entityName: String = "Comic"
23 |
24 | }
25 |
26 |
27 | extension Comic: CustomStringConvertible {
28 | override var description: String {
29 | return """
30 | name : \(name)
31 | pageCount: \(imageNames.count ?? 0)
32 | comic group: \(ofComicGroup)
33 | """
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/wutComicReader/CoreData/ComicGroup+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComicGroup+CoreDataClass.swift
3 | //
4 | //
5 | // Created by Sha Yan on 1/27/20.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | @objc(ComicGroup)
13 | public class ComicGroup: NSManagedObject {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/wutComicReader/CoreData/ComicGroup+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComicGroup+CoreDataProperties.swift
3 | //
4 | //
5 | // Created by Sha Yan on 2/9/20.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension ComicGroup {
14 | @NSManaged public var id: UUID
15 | @NSManaged public var isForNewComics: Bool
16 | @NSManaged public var name: String
17 | @NSManaged public var comics: NSOrderedSet?
18 | }
19 |
20 | extension ComicGroup {
21 | static let entityName = "ComicGroup"
22 | }
23 |
24 | extension ComicGroup: CustomStringConvertible {
25 | var description: String {
26 | return """
27 | name: \(name)
28 | comics: \(comics)
29 | """
30 | }
31 | }
32 |
33 | // MARK: Generated accessors for comics
34 | extension ComicGroup {
35 |
36 | @objc(insertObject:inComicsAtIndex:)
37 | @NSManaged public func insertIntoComics(_ value: Comic, at idx: Int)
38 |
39 | @objc(removeObjectFromComicsAtIndex:)
40 | @NSManaged public func removeFromComics(at idx: Int)
41 |
42 | @objc(insertComics:atIndexes:)
43 | @NSManaged public func insertIntoComics(_ values: [Comic], at indexes: NSIndexSet)
44 |
45 | @objc(removeComicsAtIndexes:)
46 | @NSManaged public func removeFromComics(at indexes: NSIndexSet)
47 |
48 | @objc(replaceObjectInComicsAtIndex:withObject:)
49 | @NSManaged public func replaceComics(at idx: Int, with value: Comic)
50 |
51 | @objc(replaceComicsAtIndexes:withComics:)
52 | @NSManaged public func replaceComics(at indexes: NSIndexSet, with values: [Comic])
53 |
54 | @objc(addComicsObject:)
55 | @NSManaged public func addToComics(_ value: Comic)
56 |
57 | @objc(removeComicsObject:)
58 | @NSManaged public func removeFromComics(_ value: Comic)
59 |
60 | @objc(addComics:)
61 | @NSManaged public func addToComics(_ values: NSOrderedSet)
62 |
63 | @objc(removeComics:)
64 | @NSManaged public func removeFromComics(_ values: NSOrderedSet)
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/wutComicReader/CoreData/coredata.xcdatamodeld/.xccurrentversion:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | _XCCurrentVersionName
6 | coredata v2.xcdatamodel
7 |
8 |
9 |
--------------------------------------------------------------------------------
/wutComicReader/CoreData/coredata.xcdatamodeld/coredata v2.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/wutComicReader/CoreData/coredata.xcdatamodeld/coredata.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/wutComicReader/Cores/AppFileManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppFileManager.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 12/1/19.
6 | // Copyright © 2019 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import CoreData
12 |
13 |
14 | enum fileManagerError : Error {
15 | case deleteFailed
16 | case fetchFailed
17 | }
18 |
19 | protocol AppFileManagerErrorDelegate: class {
20 | func errorsAccured(errors: [AppFileManager.AppFileManagerError])
21 | }
22 |
23 | extension AppFileManager {
24 | struct AppFileManagerError {
25 | let type: AppFileManagerErrorType
26 | let comicName: String?
27 | }
28 | enum AppFileManagerErrorType {
29 | case cantWriteOnCoreData
30 | case cantReadComicDirectory
31 | case emptyFile
32 | }
33 | }
34 |
35 |
36 | class AppFileManager {
37 |
38 | //MARK:- Variables
39 |
40 | let comicDirectory: URL
41 | let userDirectory: URL
42 |
43 | private var fileManager = FileManager.default
44 | var dataService: DataService!
45 | weak var progressDelegate: ProgressDelegate?
46 |
47 | var errors = [AppFileManagerError]()
48 | weak var errorDelegate: AppFileManagerErrorDelegate?
49 |
50 | //MARK:- Functions
51 |
52 | init(dataService: DataService, userDirectory: URL, comicDirectory: URL) {
53 | self.dataService = dataService
54 | self.comicDirectory = comicDirectory
55 | self.userDirectory = userDirectory
56 |
57 | }
58 |
59 | func deleteAllUserDirectoryContent() throws {
60 | try fileManager.contentsOfDirectory(at: userDirectory, includingPropertiesForKeys: nil)
61 | .forEach({ (url) in
62 | try fileManager.removeItem(at: url)
63 | })
64 | }
65 |
66 | func deleteFileInTheUserDiractory(withName fileName : String) throws{
67 | if fileManager.subpaths(atPath: userDirectory.path)!.contains(fileName + ".cbz") {
68 | try fileManager.removeItem(at: userDirectory.appendingPathComponent(fileName + ".cbz"))
69 | }else if fileManager.subpaths(atPath: userDirectory.path)!.contains(fileName + ".cbr") {
70 | try fileManager.removeItem(at: userDirectory.appendingPathComponent(fileName + ".cbr"))
71 | }
72 | }
73 |
74 | func deleteFileInTheAppDiractory(withName fileName : String) throws{
75 | try fileManager.removeItem(at: comicDirectory.appendingPathComponent(fileName))
76 | }
77 |
78 | func filesInUserDiractory() -> [URL]? {
79 | try? fileManager.contentsOfDirectory(at: userDirectory, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
80 | }
81 |
82 | ///write extracted comics in the comic diractory on core data
83 | ///skip comics that already been added to core data
84 |
85 | func writeNewComicsOnCoreData(){
86 |
87 |
88 | guard let comicDiractories = try? fileManager.contentsOfDirectory(at: comicDirectory, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) else {
89 | errors.append(AppFileManagerError(type: .cantReadComicDirectory, comicName: nil))
90 | return
91 | }
92 |
93 |
94 | for diractory in comicDiractories {
95 |
96 | let comicName = diractory.lastPathComponent
97 |
98 | let extractionDirectory = ExtractionDirectory(directoryName: comicName)
99 |
100 | if !dataService.comicAlreadyExistedInCoreData(withName: comicName) {
101 |
102 | // comicImageNames = [("original" or "thumbnail") + image name]
103 | guard
104 | let comicImageNames = try? sortedOriginalImagesSubpaths(in: extractionDirectory),
105 | let thumbnailImages = try? sortedThumbnailImagesSubpaths(in: extractionDirectory),
106 | !comicImageNames.isEmpty,
107 | !thumbnailImages.isEmpty
108 | else {
109 | try? deleteFileInTheUserDiractory(withName: comicName)
110 | try? deleteFileInTheAppDiractory(withName: comicName)
111 | errors.append(AppFileManagerError(type: .emptyFile, comicName: comicName))
112 | continue
113 | }
114 |
115 | do{
116 | let groupName = try? extractionDirectory.readMetaData().groupName
117 | try dataService.addNewComic(name: comicName,
118 | imageNames: comicImageNames,
119 | thumbnailNames: thumbnailImages,
120 | toComicGroupWithName: groupName)
121 | }catch {
122 | errors.append(AppFileManagerError(type: .cantWriteOnCoreData, comicName: comicName))
123 | continue
124 | }
125 | }
126 | }
127 |
128 | if !errors.isEmpty { errorDelegate?.errorsAccured(errors: errors) }
129 | errors.removeAll()
130 | }
131 |
132 |
133 |
134 | func moveFilesToUserDiractory(urls: [URL]) throws {
135 | do {
136 | for url in urls {
137 |
138 | let comicName = url.fileName()
139 | progressDelegate?.newFileAboutToCopy(withName: comicName)
140 | try fileManager.moveItem(at: url, to: URL.userDiractory.appendingPathComponent(url.lastPathComponent))
141 |
142 | }
143 | }catch let err {
144 |
145 | throw err
146 | }
147 | }
148 |
149 | //MARK:- private Functions
150 |
151 | // return an array of original/imageName
152 | private func imageSubPaths(InDirectoryWithURL url: URL) throws -> [String] {
153 |
154 | return try fileManager.subpathsOfDirectory(atPath: url.path)
155 |
156 | .filter {string -> Bool in
157 | for format in validImageFormats where string.contains("." + format) {
158 | return true
159 | }
160 | return false
161 | }
162 |
163 | }
164 |
165 | private func sortedOriginalImagesSubpaths(in extractonDirectory: ExtractionDirectory) throws -> [String] {
166 | return try imageSubPaths(InDirectoryWithURL: extractonDirectory.originalImagesDirectoryURL)
167 | .map {ExtractionDirectory.originalImagesDirectoryName + "/" + $0}
168 | .sorted()
169 | }
170 |
171 | private func sortedThumbnailImagesSubpaths(in extractonDirectory: ExtractionDirectory) throws -> [String] {
172 | return try imageSubPaths(InDirectoryWithURL: extractonDirectory.thumbnailImagesDirectoryURL)
173 | .map {ExtractionDirectory.thumbnailImagesDirectoryName + "/" + $0}
174 | .sorted()
175 | }
176 |
177 |
178 | func makeAppDirectory() throws{
179 | try fileManager.createDirectory(at: comicDirectory, withIntermediateDirectories: true, attributes: nil)
180 | }
181 |
182 | func DidComicAlreadyExistInComicDiractory(name: String) -> Bool {
183 | do
184 | {
185 | let appDirectoryComics = try fileManager.contentsOfDirectory(atPath: comicDirectory.path)
186 | return appDirectoryComics.contains(name)
187 | }catch{
188 | return false
189 | }
190 | }
191 |
192 |
193 | }
194 |
195 |
196 |
--------------------------------------------------------------------------------
/wutComicReader/Cores/AppState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppState.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 4/7/21.
6 | // Copyright © 2021 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | final class AppState {
13 | static let main = AppState()
14 | let font: AppFont
15 | private let storage = UserDefaults()
16 |
17 | @Published private(set) var readerTheme: ReaderTheme!
18 | @Published private(set) var showComicNames: Bool!
19 | @Published private(set) var readerPageMode: ReaderPageMode!
20 |
21 | init() {
22 | font = SystemFont()
23 | readerTheme = getTheme()
24 | showComicNames = getShouldShowComics()
25 | readerPageMode = getbookReaderPageMode()
26 |
27 | }
28 |
29 | func setTheme(to theme: ReaderTheme) {
30 | storage.setValue(theme.rawValue, forKey: Keys.apptheme)
31 | self.readerTheme = theme
32 | }
33 |
34 | private func getTheme() -> ReaderTheme {
35 | if let id = storage.string(forKey: Keys.apptheme),
36 | let theme = ReaderTheme.init(rawValue: id) {
37 | return theme
38 | }else {
39 | return .system
40 | }
41 | }
42 |
43 | func setShouldShowComicNames(to show: Bool) {
44 | storage.setValue(show, forKey: Keys.showComicNames)
45 | showComicNames = show
46 |
47 | }
48 |
49 | func setbookReaderPageMode(_ mode: ReaderPageMode) {
50 | storage.set(mode.rawValue, forKey: Keys.bookReaderPageMode)
51 | readerPageMode = mode
52 | }
53 |
54 | private func getShouldShowComics() -> Bool {
55 | storage.bool(forKey: Keys.showComicNames)
56 | }
57 |
58 | private func getbookReaderPageMode() -> ReaderPageMode {
59 | ReaderPageMode(rawValue: storage.integer(forKey: Keys.bookReaderPageMode)) ?? .single
60 | }
61 |
62 | func didAppLaunchedForFirstTime() -> Bool {
63 | let didLaunchedBefore = storage.bool(forKey: Keys.appDidLunchedBefore)
64 | return !didLaunchedBefore
65 | }
66 |
67 | func setAppDidLaunchedForFirstTime() {
68 | storage.set(true, forKey: Keys.appDidLunchedBefore)
69 | }
70 |
71 | //FIXME: WHAT THE ACTUAL FUCK
72 | func readerPresentForFirstTime() -> Bool {
73 | let didPresentBefore = storage.bool(forKey: Keys.readerDidPresentedBefore)
74 | if !didPresentBefore {
75 | storage.set(true, forKey: Keys.readerDidPresentedBefore)
76 | return true
77 | }else{
78 | return false
79 | }
80 | }
81 |
82 |
83 | }
84 |
85 | extension AppState {
86 | private enum Keys {
87 | static let apptheme = "readerTheme"
88 | static let showComicNames = "showComicNames"
89 | static let appDidLunchedBefore = "appDidLunchedBefore"
90 | static let readerDidPresentedBefore = "readerDidPresentedBefore"
91 | static let bookReaderPageMode = "bookReaderPageMode"
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/wutComicReader/Cores/Cores.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoresFactory.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 3/13/21.
6 | // Copyright © 2021 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class Cores {
12 | static let main = Cores()
13 |
14 | let extractor: ComicExteractor
15 | let dataService: DataService
16 | let appfileManager: AppFileManager
17 |
18 | private init() {
19 | extractor = ComicExteractor(userDirectory: URL.userDiractory,
20 | comicDirectory: URL.comicDiractory)
21 |
22 | dataService = DataService()
23 |
24 | appfileManager = AppFileManager(dataService: dataService,
25 | userDirectory: URL.userDiractory,
26 | comicDirectory: URL.comicDiractory)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/wutComicReader/Cores/ExtractionDirectory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExtractionDirectory.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 1/25/21.
6 | // Copyright © 2021 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct ExtractionDirectory {
12 | let baseURL: URL
13 |
14 | struct MetaData: Codable {
15 | let groupName: String
16 | }
17 |
18 | init(directoryName: String, baseURL: URL? = nil) {
19 | let url = baseURL != nil ? baseURL! : URL.comicDiractory
20 | self.baseURL = url.appendingPathComponent(directoryName)
21 | }
22 |
23 | var originalImagesDirectoryURL: URL {
24 | baseURL.appendingPathComponent(ExtractionDirectory.originalImagesDirectoryName)
25 | }
26 | var thumbnailImagesDirectoryURL: URL {
27 | baseURL.appendingPathComponent(ExtractionDirectory.thumbnailImagesDirectoryName)
28 | }
29 | var metaDataURL: URL {
30 | baseURL.appendingPathComponent(ExtractionDirectory.metaDataFileName).appendingPathExtension("json")
31 | }
32 |
33 | static var originalImagesDirectoryName = "original"
34 | static var thumbnailImagesDirectoryName = "thumbnail"
35 | static private var metaDataFileName = "metadata"
36 |
37 | func createDirectories() throws {
38 |
39 | try FileManager.default.createDirectory(at: baseURL,
40 | withIntermediateDirectories: true,
41 | attributes: nil)
42 | try FileManager.default.createDirectory(at: originalImagesDirectoryURL,
43 | withIntermediateDirectories: true,
44 | attributes: nil)
45 | try FileManager.default.createDirectory(at: thumbnailImagesDirectoryURL,
46 | withIntermediateDirectories: true,
47 | attributes: nil)
48 |
49 | }
50 |
51 | func write(metaData: MetaData) throws {
52 | let data = try JSONEncoder().encode(metaData)
53 | try data.write(to: metaDataURL)
54 | }
55 |
56 | func readMetaData() throws -> MetaData {
57 | let data = try Data(contentsOf: metaDataURL)
58 | return try JSONDecoder().decode(MetaData.self, from: data)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/wutComicReader/Cores/ImageResizer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageResizer.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 3/10/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import AVFoundation
12 |
13 | class ImageResizer {
14 | private var imagePaths: [String]?
15 | private var diractoryForResizedImages: URL?
16 |
17 | convenience init(for imagePaths: [String], saveTo path: URL) {
18 | self.init()
19 | self.imagePaths = imagePaths
20 | self.diractoryForResizedImages = path
21 |
22 | }
23 |
24 | func startResizing(progress: (Double)->()) {
25 | guard
26 | let paths = imagePaths,
27 | let _ = diractoryForResizedImages
28 | else { return }
29 |
30 | var count: Double = 0
31 | for path in paths {
32 |
33 | if let image = UIImage(contentsOfFile: path) {
34 |
35 | let isImageisDoubleSplash = image.size.width > image.size.height
36 | if count == 0 {
37 | resize(image,
38 | withName: imageName(fromPath: path),
39 | withSize: CGSize(width: (isImageisDoubleSplash ? 600 : 300), height: 450))
40 | }else{
41 | resize(image,
42 | withName: imageName(fromPath: path),
43 | withSize: CGSize(width: (isImageisDoubleSplash ? 200 : 100), height: 150))
44 | }
45 | count += 1
46 | progress(count / Double(paths.count))
47 | }
48 | }
49 | }
50 |
51 | private func resize(_ image: UIImage, withName name: String ,withSize resizeSize: CGSize) {
52 |
53 | let render = UIGraphicsImageRenderer(size: resizeSize)
54 | let data = render.jpegData(withCompressionQuality: 0.5) { (context) in
55 | image.draw(in: CGRect(origin: .zero, size: resizeSize))
56 | }
57 | do{
58 | try data.write(to: diractoryForResizedImages!.appendingPathComponent(name))
59 | }catch let error{
60 | print("error happend in resizing an image" + error.localizedDescription)
61 | }
62 |
63 | }
64 |
65 | private func imageName(fromPath path: String) -> String {
66 |
67 | let lastSlashIndex = path.lastIndex(of: "/")!
68 | return path.substring(from: lastSlashIndex)
69 | }
70 |
71 |
72 | func resize(_ image: UIImage?, to size: CGSize) -> UIImage? {
73 | guard let img = image else { return nil }
74 |
75 | let render = UIGraphicsImageRenderer(size: size)
76 | let resizedImage = render.image(actions: { _ in
77 | img.draw(in: CGRect(origin: .zero, size: size))
78 | })
79 | return resizedImage
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/wutComicReader/Cores/IndexSelectionManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IndexSelectionManager.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 11/21/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Combine
11 |
12 | class IndexSelectionManager {
13 | public var publisher: AnyPublisher,Never>!
14 | public var indexes: Set { _indexes }
15 | @Published private var _indexes = Set()
16 | @Published var lastIndexThatChanged: IndexPath?
17 |
18 | var cancellable: AnyCancellable?
19 | init() {
20 | publisher = $_indexes.eraseToAnyPublisher()
21 |
22 | cancellable = $_indexes.sink { (set) in
23 | print("✅ Selected Comics -> ", set)
24 | }
25 | }
26 |
27 | func insert(_ index: IndexPath) {
28 | _indexes.insert(index)
29 | lastIndexThatChanged = index
30 | }
31 |
32 | func remove(_ index:IndexPath) {
33 | _indexes.remove(index)
34 | lastIndexThatChanged = index
35 | }
36 |
37 | func removeAll() {
38 | _indexes.removeAll()
39 | }
40 |
41 | func isSelectedAllItemsOf(collectionView: UICollectionView, inSection section: Int) -> Bool {
42 | _indexes.filter{$0.section == section}.count == collectionView.numberOfItems(inSection: section)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/wutComicReader/Extensions/+CollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // +CollectionView.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 2/27/1401 AP.
6 | // Copyright © 1401 AP wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UICollectionViewCell {
13 | static var id: String {
14 | NSStringFromClass(self)
15 | }
16 | }
17 |
18 | extension UITableViewCell {
19 | static var id: String {
20 | NSStringFromClass(self)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/wutComicReader/Extensions/+Combine.swift:
--------------------------------------------------------------------------------
1 | //
2 | // +Combine.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 5/8/1401 AP.
6 | // Copyright © 1401 AP wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Combine
11 | import CoreMedia
12 |
13 | extension Publisher where Failure == Never {
14 | func weakAssign(
15 | to keyPath: ReferenceWritableKeyPath,
16 | on object: T) -> AnyCancellable {
17 | return sink { [weak object] value in
18 | object?[keyPath: keyPath] = value
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/wutComicReader/Extensions/+NSLayoutContraint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // +NSLayoutContraint.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 10/12/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension NSLayoutConstraint {
13 | func withHighPiority() -> NSLayoutConstraint {
14 | let piority = UILayoutPriority(850)
15 | self.priority = piority
16 | return self
17 | }
18 |
19 | func withLowPiority() -> NSLayoutConstraint {
20 | let piority = UILayoutPriority(250)
21 | self.priority = piority
22 | return self
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/wutComicReader/Extensions/+UIImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // +UIImage.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 5/17/1401 AP.
6 | // Copyright © 1401 AP wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import CoreGraphics
12 |
13 | //TODO: Make it a class
14 | extension UIImage {
15 | func getPixelColor(pos: CGPoint) -> UIColor? {
16 |
17 | guard let pixelData = self.cgImage?.dataProvider?.data else { return nil }
18 | let data: UnsafePointer = CFDataGetBytePtr(pixelData)
19 |
20 | let pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4
21 | //FIXME: pixelfInfo ois out of the range probably
22 |
23 | let r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
24 | let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
25 | let b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
26 | let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)
27 |
28 | return UIColor(red: r, green: g, blue: b, alpha: a)
29 |
30 | }
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/wutComicReader/Extensions/+URL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // +URL.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 10/3/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | fileprivate var comicDiractoryName = "ComicFiles"
12 |
13 | extension URL {
14 | static var comicDiractory: URL {
15 | return FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!.appendingPathComponent(comicDiractoryName)
16 | }
17 | static var userDiractory: URL {
18 | return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
19 | }
20 |
21 | func fileName() -> String {
22 | return self.deletingPathExtension().lastPathComponent
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/wutComicReader/Extensions/ComicImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComicImage.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 3/11/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 |
12 | //TODO: Move it to somewhere else!
13 | struct ComicImage: Equatable {
14 | var pageNumber: Int?
15 | var isDoubleSplash = false
16 | let path: String
17 |
18 | init(_ comic: Comic?, withImageName imageName: String?) {
19 | let comicname : String = comic?.name ?? ""
20 | let imagename: String = imageName ?? ""
21 |
22 | path = URL.comicDiractory.appendingPathComponent(comicname).appendingPathComponent( imagename).path
23 |
24 | if let imageSize = UIImage(contentsOfFile: path)?.size {
25 | isDoubleSplash = imageSize.width > imageSize.height
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/wutComicReader/Extensions/Helper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Helper.swift
3 | // wutComicReader
4 | //
5 | // Created by Shayan on 7/10/19.
6 | // Copyright © 2019 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import AVFoundation
12 |
13 |
14 |
15 | extension UIView {
16 |
17 | func makeDropShadow(scale: Bool = true , shadowOffset: CGSize , opacity: Float, radius: CGFloat) {
18 | layer.shadowColor = UIColor.black.cgColor
19 | layer.shadowOffset = shadowOffset
20 | layer.shadowRadius = radius
21 | layer.shadowOpacity = opacity
22 | clipsToBounds = true
23 | layer.masksToBounds = false
24 | layer.shouldRasterize = true
25 | layer.rasterizationScale = scale ? UIScreen.main.scale : 1
26 | }
27 |
28 | func makeBoundsDropShadow(shadowOffset: CGSize , opacity: Float, radius: CGFloat) {
29 | let shadowPath = UIBezierPath(rect: bounds)
30 | layer.masksToBounds = false
31 | layer.shadowColor = UIColor.black.cgColor
32 | layer.shadowOffset = shadowOffset
33 | layer.shadowOpacity = opacity
34 | layer.shadowRadius = radius
35 | layer.shadowPath = shadowPath.cgPath
36 | }
37 | }
38 |
39 | extension UIResponder {
40 | public var parentViewController: UIViewController? {
41 | return next as? UIViewController ?? next?.parentViewController
42 | }
43 | }
44 |
45 | class ZoomGestureRecognizer: UITapGestureRecognizer {
46 | var point: CGPoint?
47 |
48 | override func touchesBegan(_ touches: Set, with event: UIEvent) {
49 | super.touchesBegan(touches, with: event)
50 | point = touches.first?.location(in: view)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/wutComicReader/Extensions/UIKitPreview.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIControllerPreview.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 3/13/21.
6 | // Copyright © 2021 wutup. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct UIKitPreview: UIViewRepresentable {
12 |
13 | typealias UIViewType = UIView
14 | let view: UIViewType
15 |
16 | func makeUIView(context: Context) -> UIView {
17 | view
18 | }
19 |
20 | func updateUIView(_ uiViewController: UIViewType, context: Context) {
21 |
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/Extensions/VCAlert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VCAlert.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 4/26/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIViewController {
12 | func showAlert(with title: String, description: String, dismissButtonTitle: String? = "OK") {
13 |
14 | let alert = UIAlertController(title: title, message: description, preferredStyle: .alert)
15 |
16 | let actionButton = UIAlertAction(title: dismissButtonTitle, style: .default, handler: { _ in
17 | alert.dismiss(animated: true, completion: nil)
18 | })
19 | alert.addAction(actionButton)
20 | present(alert, animated: true)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/wutComicReader/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDocumentTypes
8 |
9 |
10 | CFBundleTypeName
11 | Comic
12 | LSHandlerRank
13 | Default
14 | LSItemContentTypes
15 |
16 | com.wutup.comic
17 |
18 |
19 |
20 | CFBundleExecutable
21 | $(EXECUTABLE_NAME)
22 | CFBundleIdentifier
23 | $(PRODUCT_BUNDLE_IDENTIFIER)
24 | CFBundleInfoDictionaryVersion
25 | 6.0
26 | CFBundleName
27 | $(PRODUCT_NAME)
28 | CFBundlePackageType
29 | APPL
30 | CFBundleShortVersionString
31 | $(MARKETING_VERSION)
32 | CFBundleVersion
33 | $(CURRENT_PROJECT_VERSION)
34 | LSRequiresIPhoneOS
35 |
36 | LSSupportsOpeningDocumentsInPlace
37 |
38 | UIFileSharingEnabled
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIMainStoryboardFile
43 | Main
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UIStatusBarStyle
49 | UIStatusBarStyleLightContent
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 | UTExportedTypeDeclarations
64 |
65 |
66 | UTTypeConformsTo
67 |
68 | public.data
69 | public.archive
70 |
71 | UTTypeDescription
72 | ComicBook
73 | UTTypeIconFiles
74 |
75 | UTTypeIdentifier
76 | com.wutup.comic
77 | UTTypeTagSpecification
78 |
79 | public.filename-extension
80 |
81 | cbr
82 | CBR
83 | cbz
84 | CBZ
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/wutComicReader/ProgressViews/CircleProgressView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CircleProgressView.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 2/19/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | class CircleProgressView: UIView {
13 |
14 | //MARK:- Variables
15 |
16 | private lazy var progressCircleShape = CAShapeLayer()
17 | private lazy var backgroundCircleShape = CAShapeLayer()
18 | private var backgroundCircleRect: CGRect?
19 |
20 | var trackCircleColor: CGColor? {
21 | didSet{
22 | backgroundCircleShape.fillColor = trackCircleColor
23 | }
24 | }
25 |
26 | var progressCircleColor: CGColor? {
27 | didSet{
28 | progressCircleShape.fillColor = progressCircleColor
29 | }
30 | }
31 |
32 | var progressValue: CGFloat? {
33 | didSet{
34 | layoutSubviews()
35 | }
36 | }
37 |
38 | var strokeWidth: CGFloat = 0
39 |
40 | //MARK:- Functions
41 |
42 | override init(frame: CGRect) {
43 | super.init(frame: frame)
44 |
45 | }
46 | override func layoutSubviews() {
47 | drawBackgroundCircle()
48 | if let value = progressValue {
49 | drawProgressCircle(withProgress: value)
50 | }
51 | }
52 |
53 | convenience init(withProgress value: CGFloat, InnerStroke: CGFloat = 0){
54 | self.init()
55 | self.strokeWidth = InnerStroke
56 | self.progressValue = value
57 | }
58 |
59 | private func drawProgressCircle(withProgress value: CGFloat){
60 | guard 0 <= value && value <= 1 else { return }
61 | progressCircleShape.removeFromSuperlayer()
62 |
63 | let circle = UIBezierPath()
64 | let center = CGPoint(x: bounds.width * 0.5, y: bounds.height * 0.5)
65 | circle.move(to: center)
66 | circle.addArc(withCenter: center,
67 | radius: frame.size.height / 2 - strokeWidth,
68 | startAngle: -(CGFloat.pi * 0.5),
69 | endAngle: -(CGFloat.pi * 0.5) + 2 * value * CGFloat.pi,
70 | clockwise: true)
71 |
72 | progressCircleShape.path = circle.cgPath
73 | progressCircleShape.fillColor = progressCircleColor ?? defaultBlueColor.cgColor
74 | layer.addSublayer(progressCircleShape)
75 | scaleToBounds(progressCircleShape)
76 |
77 | }
78 |
79 | private func drawBackgroundCircle(){
80 | let center = CGPoint(x: bounds.width * 0.5, y: bounds.height * 0.5)
81 | let circle = UIBezierPath(arcCenter: center,
82 | radius: frame.size.height / 2,
83 | startAngle: 0,
84 | endAngle: 2 * CGFloat.pi,
85 | clockwise: true)
86 |
87 | backgroundCircleShape.path = circle.cgPath
88 | backgroundCircleShape.fillColor = trackCircleColor ?? defaultGrayColor.cgColor
89 | layer.addSublayer(backgroundCircleShape)
90 |
91 | backgroundCircleRect = circle.cgPath.boundingBox
92 | scaleToBounds(backgroundCircleShape)
93 | }
94 |
95 | private func scaleToBounds(_ shape:CAShapeLayer){
96 | let xScale = bounds.width / backgroundCircleRect!.width
97 | let yScale = bounds.height / backgroundCircleRect!.height
98 | shape.transform = CATransform3DMakeScale(xScale, yScale, 1.0)
99 | }
100 |
101 | required init?(coder: NSCoder) {
102 | super.init(coder: coder)
103 | }
104 |
105 |
106 | //MARK:- Defaults Colors
107 |
108 | private var defaultBlueColor : UIColor = {
109 | if #available(iOS 13.0, *) {
110 | return .systemBlue
111 | }else{
112 | return .blue
113 | }
114 | }()
115 |
116 |
117 | private var defaultGrayColor : UIColor = {
118 | if #available(iOS 13.0, *) {
119 | return .systemGray4
120 | }else{
121 | return .lightGray
122 | }
123 | }()
124 |
125 | }
126 |
127 |
--------------------------------------------------------------------------------
/wutComicReader/ProgressViews/RoundedProgressView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoundedProgressView.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 2/19/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | class RoundedProgressView: UIView {
13 | //MARK:- Variables
14 |
15 | var trackViewImage: UIImage?{
16 | didSet{
17 | trackView.image = trackViewImage
18 | }
19 | }
20 | var progressViewImage: UIImage?{
21 | didSet{
22 | progressView.image = progressViewImage
23 | }
24 | }
25 |
26 | var trackViewTint: UIColor?{
27 | didSet{
28 | trackView.backgroundColor = trackViewTint
29 | }
30 | }
31 |
32 | var progressViewTint: UIColor?{
33 | didSet{
34 | progressView.backgroundColor = progressViewTint
35 | }
36 | }
37 |
38 | var loadAnimationSpeed = 0.2
39 |
40 | //MARK:- UI variables
41 |
42 | private lazy var trackView: UIImageView = {
43 | let view = UIImageView()
44 | view.clipsToBounds = true
45 | view.backgroundColor = defaultGrayColor
46 | view.translatesAutoresizingMaskIntoConstraints = false
47 | return view
48 | }()
49 |
50 | private lazy var progressView: UIImageView = {
51 | let view = UIImageView()
52 | view.clipsToBounds = true
53 | view.backgroundColor = defaultBlueColor
54 | view.translatesAutoresizingMaskIntoConstraints = false
55 | return view
56 | }()
57 | var progressViewWidth: NSLayoutConstraint!
58 |
59 |
60 | //MARK:- Functions
61 |
62 | override init(frame: CGRect) {
63 | super.init(frame: frame)
64 | setupDesign()
65 | }
66 |
67 | private func setupDesign(){
68 | addSubview(trackView)
69 | trackView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
70 | trackView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
71 | trackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
72 | trackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
73 |
74 | trackView.addSubview(progressView)
75 | progressView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
76 | progressView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
77 | progressView.topAnchor.constraint(equalTo: topAnchor).isActive = true
78 | progressViewWidth = progressView.widthAnchor.constraint(equalToConstant: 0)
79 | progressViewWidth.isActive = true
80 |
81 |
82 | progressView.clipsToBounds = true
83 | trackView.clipsToBounds = true
84 | clipsToBounds = true
85 |
86 | }
87 |
88 | override func layoutSubviews() {
89 | progressView.layer.cornerRadius = bounds.height * 0.5
90 | trackView.layer.cornerRadius = bounds.height * 0.5
91 | }
92 |
93 | func setProgress(to value: CGFloat, animated: Bool) {
94 |
95 | guard value <= 1 && value >= 0 else { return }
96 | self.progressViewWidth.constant = self.bounds.size.width * CGFloat(value)
97 | UIView.animate(withDuration: animated ? loadAnimationSpeed : 0.0) { [unowned self] in
98 | self.layoutIfNeeded()
99 | }
100 |
101 | }
102 |
103 | required init?(coder: NSCoder) {
104 | super.init(coder: coder)
105 | }
106 |
107 |
108 | //MARK:- Default Colors
109 |
110 | private var defaultBlueColor : UIColor = {
111 | if #available(iOS 13.0, *) {
112 | return .systemBlue
113 | }else{
114 | return .blue
115 | }
116 | }()
117 |
118 |
119 | private var defaultGrayColor : UIColor = {
120 | if #available(iOS 13.0, *) {
121 | return .systemGray4
122 | }else{
123 | return .lightGray
124 | }
125 | }()
126 |
127 |
128 | }
129 |
130 |
--------------------------------------------------------------------------------
/wutComicReader/Storyboards/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Book Reader/BookReader+PageVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookReader+PageVC.swift
3 | // wutComicReader
4 | //
5 | // Created by Shayan on 10/26/24.
6 | // Copyright © 2024 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | extension BookReaderVC {
11 |
12 |
13 | func setupPageController(pageMode: ReaderPageMode) {
14 | bookPageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
15 | bookPageViewController.delegate = self
16 | bookPageViewController.dataSource = self
17 |
18 | addChild(bookPageViewController)
19 | view.addSubview(bookPageViewController.view)
20 |
21 | configureBookPages(pageMode: pageMode)
22 | }
23 |
24 |
25 | func configureBookPages(pageMode: ReaderPageMode) {
26 |
27 | bookPages.removeAll()
28 |
29 | switch pageMode {
30 | case .single:
31 | for bookImage in bookSingleImages {
32 | let bookPage = BookPage()
33 | bookPage.pageNumber = bookSingleImages.firstIndex(where: { $0 == bookImage })
34 | bookPage.image1 = bookImage
35 |
36 | bookPages.append(bookPage)
37 | }
38 | case .double:
39 | for bookImages in bookDoubleImages {
40 | let bookPage = BookPage()
41 |
42 | bookPage.pageNumber = bookDoubleImages.firstIndex(where: { $0 == bookImages })
43 | bookPage.image1 = bookImages.0
44 | bookPage.image2 = bookImages.1
45 |
46 | bookPages.append(bookPage)
47 | }
48 | }
49 |
50 |
51 | }
52 |
53 | // func doublePageIndexForPage(withNumber pageNumber:Int) -> Int? {
54 | // if pageNumber < bookSingleImages.count {
55 | // let number = bookSingleImages[pageNumber].pageNumber
56 | //
57 | // return bookDoubleImages.firstIndex { touple -> Bool in
58 | // return (touple.0?.pageNumber == number || touple.1?.pageNumber == number)
59 | // }
60 | //
61 | // }
62 | // return nil
63 | // }
64 |
65 | }
66 |
67 | //MARK:- book Pages Delegates
68 |
69 | extension BookReaderVC : UIPageViewControllerDataSource , UIPageViewControllerDelegate {
70 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController:
71 | UIViewController) -> UIViewController? {
72 |
73 | if let index = bookPages.firstIndex(of: viewController as! BookPage) {
74 | if index < bookPages.count - 1 {
75 | return bookPages[index + 1]
76 |
77 | }else{
78 | return nil
79 | }
80 | }
81 | return nil
82 | }
83 |
84 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
85 | if let index = bookPages.firstIndex(of: viewController as! BookPage) {
86 | if index > 0 {
87 | return bookPages[index - 1]
88 | }else {
89 | return nil
90 | }
91 | }
92 | return nil
93 | }
94 |
95 |
96 | func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
97 | // setLastViewedPage(toPageWithNumber: pendingPages.first?.image1?.pageNumber ?? 0)
98 |
99 | // print("--------- started transition with view size : (\(page.view.bounds.size)")
100 |
101 |
102 | }
103 |
104 | func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
105 |
106 | if completed {
107 | let currentPage = pageViewController.viewControllers?.first as! BookPage
108 | if let pageNumber1 = currentPage.image1?.pageNumber{
109 | setLastViewedPage(toPageWithNumber: pageNumber1, withAnimate: true)
110 | }else if let pageNumber2 = currentPage.image2?.pageNumber {
111 | setLastViewedPage(toPageWithNumber: pageNumber2, withAnimate: true)
112 | }
113 |
114 | }
115 |
116 | }
117 |
118 |
119 |
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Book Reader/BookReaderFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookReaderFactory.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 4/1/1401 AP.
6 | // Copyright © 1401 AP wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | struct BookReaderFactory {
13 |
14 | let comic: Comic
15 | let modalPresentationStyle: UIModalPresentationStyle
16 | var loadingHandler: (() -> Void)?
17 | var comicReadingProgressDidChanged: ((_ comic: Comic, _ lastPageHaveRead: Int) -> Void)?
18 |
19 |
20 | init(comic: Comic, modalPresentationStyle: UIModalPresentationStyle = .fullScreen) {
21 | self.comic = comic
22 | self.modalPresentationStyle = modalPresentationStyle
23 | }
24 |
25 |
26 | func build(complition: @escaping (BookReaderVC) -> Void) {
27 | let readerVC = UIStoryboard(name: "Main", bundle: nil)
28 | .instantiateViewController(withIdentifier: "bookReader") as! BookReaderVC
29 | readerVC.comic = comic
30 | readerVC.modalPresentationStyle = modalPresentationStyle
31 | readerVC.comicReadingProgressDidChanged = comicReadingProgressDidChanged
32 |
33 | if (comic.imageNames?.count ?? 0) > 80 {
34 | loadingHandler?()
35 | }
36 |
37 | let dispatchQueue = DispatchQueue(label: UUID().uuidString, qos: .userInitiated)
38 | dispatchQueue.async {
39 |
40 | readerVC.bookSingleImages = createSingleBookImages()
41 | readerVC.bookDoubleImages = createDoubleBookImages()
42 | readerVC.thumbnailImages = initSinglePageThumbnails()
43 |
44 |
45 | DispatchQueue.main.async {
46 | complition(readerVC)
47 | }
48 | }
49 | }
50 |
51 | }
52 |
53 |
54 | fileprivate extension BookReaderFactory {
55 |
56 | private func createSingleBookImages() -> [ComicImage] {
57 | guard let comicPages = comic.imageNames as? [String] else { return [] }
58 | return comicPages.map { (comicPage) -> ComicImage in
59 | var bookPageImage = ComicImage(comic, withImageName: comicPage)
60 | bookPageImage.pageNumber = comicPages.firstIndex(of: comicPage)! + 1
61 | return bookPageImage
62 | }
63 |
64 | }
65 |
66 | private func createDoubleBookImages() -> [(ComicImage?, ComicImage?)]{
67 |
68 | var tempDouble: [ComicImage?] = [nil]
69 | guard let comicPages = comic.imageNames as? [String] else { return [] }
70 | var comicImages = [(ComicImage?, ComicImage?)]()
71 |
72 |
73 | func isImageInDoubleSplashSize(_ image: UIImage) -> Bool {
74 | return image.size.height < image.size.width
75 | }
76 |
77 | //creating tempDoubles
78 |
79 | for comicPage in comicPages {
80 | let index = comicPages.firstIndex(of: comicPage)!
81 | var image = ComicImage(comic, withImageName: comicPage)
82 | image.pageNumber = comicPages.firstIndex(of: comicPage)! + 1
83 |
84 | if isImageInDoubleSplashSize(UIImage(contentsOfFile: image.path) ?? UIImage()) {
85 | if index.isMultiple(of: 2) {
86 | tempDouble.append(contentsOf: [nil , image , nil])
87 | }else{
88 | tempDouble.append(contentsOf: [image , nil])
89 | }
90 | }else{
91 | tempDouble.append(image)
92 | }
93 | }
94 |
95 | //creating doubleImages
96 |
97 | for index in 0 ... tempDouble.count - 1 {
98 | if index.isMultiple(of: 2){
99 | if index < tempDouble.count - 1 {
100 | if tempDouble[index] == nil && tempDouble[index + 1] == nil {} else {
101 | comicImages.append((tempDouble[index], tempDouble[index + 1]))
102 | }
103 | }else{
104 | comicImages.append((tempDouble[index] , nil))
105 | }
106 | }
107 | }
108 |
109 | return comicImages
110 |
111 | }
112 |
113 | func initSinglePageThumbnails() -> [ComicImage]{
114 | guard let thumbnails = comic.thumbnailNames as? [String] else { return [] }
115 | var pageNumber = 1
116 | return thumbnails.map { thumbnail -> ComicImage in
117 | var comicImage = ComicImage(comic, withImageName: thumbnail)
118 | comicImage.pageNumber = pageNumber
119 | pageNumber += 1
120 | return comicImage
121 | }
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Book Reader/Reader Setting/ReaderPageMode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReaderPageMode.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 6/5/1401 AP.
6 | // Copyright © 1401 AP wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum ReaderPageMode: Int, CaseIterable {
12 | case single = 1
13 | case double = 2
14 |
15 | var name: String {
16 | switch self {
17 | case .single:
18 | return "Single"
19 | case .double:
20 | return "Double"
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Book Reader/Reader Setting/ReaderSettingVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingBar.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 5/28/1401 AP.
6 | // Copyright © 1401 AP wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | class ReaderSettingVC: UINavigationController {
13 | init(settingDelegate: ReaderSettingVCDelegate? = nil) {
14 | let vc = SettingVC()
15 | vc.delegate = settingDelegate
16 | super.init(rootViewController: vc)
17 | }
18 |
19 | required init?(coder aDecoder: NSCoder) {
20 | fatalError("init(coder:) has not been implemented")
21 | }
22 | }
23 |
24 | protocol ReaderSettingVCDelegate: AnyObject {
25 | func doneButtonTapped()
26 | }
27 |
28 | fileprivate final class SettingVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
29 |
30 | weak var delegate: ReaderSettingVCDelegate?
31 |
32 | private lazy var tableView: UITableView = {
33 | let view = UITableView(frame: .zero, style: .insetGrouped)
34 | view.translatesAutoresizingMaskIntoConstraints = false
35 | return view
36 | }()
37 |
38 | private lazy var pageModeSegmentControll: UISegmentedControl = {
39 | let view = UISegmentedControl(frame: .zero, actions: ReaderPageMode.allCases.map({ pageMode in
40 | return UIAction(title: pageMode.name, handler: { [weak self] _ in
41 | self?.pageModeChanged(to: pageMode)
42 | })
43 | }))
44 | view.selectedSegmentIndex = ReaderPageMode.allCases.firstIndex(of: AppState.main.readerPageMode)!
45 | return view
46 | }()
47 |
48 | private lazy var ReaderThemeSegmentControll: UISegmentedControl = {
49 | let view = UISegmentedControl(frame: .zero, actions: ReaderTheme.allCases.map({ theme in
50 | return UIAction(title: theme.rawValue.capitalized, handler: {[weak self] _ in
51 | self?.readerThemeChanged(to: theme)
52 | })
53 | }))
54 | view.selectedSegmentIndex = ReaderTheme.allCases.firstIndex(of: AppState.main.readerTheme)!
55 | view.translatesAutoresizingMaskIntoConstraints = false
56 | return view
57 | }()
58 |
59 | private var pageModeCell = UITableViewCell()
60 | private var readerThemeCell = UITableViewCell()
61 |
62 | private let cellInset: CGFloat = 10
63 |
64 |
65 | override func viewDidLoad() {
66 | super.viewDidLoad()
67 | setupDesign()
68 | setupNavigationBar()
69 | configurePageModeCell()
70 | configureReaderThemeCell()
71 |
72 | tableView.delegate = self
73 | tableView.dataSource = self
74 | }
75 |
76 | private func setupDesign() {
77 | view.addSubview(tableView)
78 | NSLayoutConstraint.activate([
79 | tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
80 | tableView.topAnchor.constraint(equalTo: view.topAnchor),
81 | tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
82 | tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
83 | ])
84 |
85 |
86 | tableView.clipsToBounds = true
87 | tableView.layer.cornerRadius = 20
88 | }
89 |
90 | private func setupNavigationBar() {
91 | navigationItem.setRightBarButton(
92 | UIBarButtonItem(
93 | title: "Done",
94 | style: .done,
95 | target: self,
96 | action: #selector(doneButtonTapped)),
97 | animated: false)
98 | navigationController?.navigationBar.tintColor = .appMainColor
99 |
100 | title = "Reader Setting"
101 | }
102 |
103 | @objc private func doneButtonTapped() {
104 | delegate?.doneButtonTapped()
105 | }
106 |
107 |
108 | private func configurePageModeCell() {
109 |
110 | let stackView = UIStackView()
111 | stackView.axis = .vertical
112 | stackView.distribution = .fill
113 | stackView.spacing = 10
114 | stackView.translatesAutoresizingMaskIntoConstraints = false
115 |
116 | let label = UILabel()
117 | label.text = "Page mode:"
118 | label.font = AppState.main.font.body
119 | label.translatesAutoresizingMaskIntoConstraints = false
120 |
121 | pageModeCell.contentView.addSubview(stackView)
122 | NSLayoutConstraint.activate([
123 | stackView.leftAnchor.constraint(equalTo: pageModeCell.contentView.leftAnchor, constant: cellInset),
124 | stackView.rightAnchor.constraint(equalTo: pageModeCell.contentView.rightAnchor, constant: -cellInset),
125 | stackView.topAnchor.constraint(equalTo: pageModeCell.contentView.topAnchor, constant: cellInset),
126 | stackView.bottomAnchor.constraint(equalTo: pageModeCell.contentView.bottomAnchor, constant: -cellInset),
127 | ])
128 |
129 | stackView.addArrangedSubview(label)
130 | stackView.addArrangedSubview(pageModeSegmentControll)
131 |
132 | }
133 |
134 | private func configureReaderThemeCell() {
135 |
136 | let stackView = UIStackView()
137 | stackView.axis = .vertical
138 | stackView.distribution = .fill
139 | stackView.spacing = 10
140 | stackView.translatesAutoresizingMaskIntoConstraints = false
141 |
142 | let label = UILabel()
143 | label.text = "Reader theme:"
144 | label.font = AppState.main.font.body
145 | label.translatesAutoresizingMaskIntoConstraints = false
146 |
147 | readerThemeCell.contentView.addSubview(stackView)
148 | NSLayoutConstraint.activate([
149 | stackView.leftAnchor.constraint(equalTo: readerThemeCell.contentView.leftAnchor, constant: cellInset),
150 | stackView.rightAnchor.constraint(equalTo: readerThemeCell.contentView.rightAnchor, constant: -cellInset),
151 | stackView.topAnchor.constraint(equalTo: readerThemeCell.contentView.topAnchor, constant: cellInset),
152 | stackView.bottomAnchor.constraint(equalTo: readerThemeCell.contentView.bottomAnchor, constant: -cellInset),
153 | ])
154 |
155 | stackView.addArrangedSubview(label)
156 | stackView.addArrangedSubview(ReaderThemeSegmentControll)
157 |
158 | }
159 |
160 | private func readerThemeChanged(to theme: ReaderTheme) {
161 | AppState.main.setTheme(to: theme)
162 | }
163 |
164 | private func pageModeChanged(to pageMode: ReaderPageMode) {
165 | AppState.main.setbookReaderPageMode(pageMode)
166 | }
167 |
168 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
169 | return 2
170 | }
171 |
172 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
173 | if indexPath.row == 0 {
174 | return pageModeCell
175 | }
176 |
177 | if indexPath.row == 1 {
178 | return readerThemeCell
179 |
180 | }
181 |
182 | fatalError()
183 |
184 |
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Book Reader/Reader Setting/ReaderTheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReaderTheme.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 2/24/1401 AP.
6 | // Copyright © 1401 AP wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | enum ReaderTheme: String, CaseIterable {
13 | // case dynamic = "dynamic"
14 | case light = "light"
15 | case dark = "dark"
16 | case system = "system"
17 |
18 |
19 | var name: String {
20 | self.rawValue.capitalized
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Book Reader/Reader Thumbnails/ThumbnailCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // thumbnailCell.swift
3 | // wutComicReader
4 | //
5 | // Created by Shayan on 7/6/19.
6 | // Copyright © 2019 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ThumbnailCell: UICollectionViewCell {
12 |
13 |
14 | var thumbnailImage: ComicImage! {
15 | didSet{
16 | pageImageView1.image = UIImage(contentsOfFile: thumbnailImage.path)
17 | }
18 | }
19 |
20 | override var isSelected: Bool {
21 | didSet{
22 | if isSelected {
23 | choosePageAsActive()
24 | }else{
25 | choosePageAsDiactive()
26 | }
27 | }
28 | }
29 |
30 | lazy var pageImageView1 : UIImageView = {
31 | let imageview = UIImageView(frame: .zero)
32 | imageview.contentMode = .scaleAspectFill
33 | imageview.clipsToBounds = true
34 | imageview.translatesAutoresizingMaskIntoConstraints = false
35 | return imageview
36 | }()
37 |
38 |
39 | lazy var imageHolderView : UIView = {
40 | let view = UIView()
41 | view.translatesAutoresizingMaskIntoConstraints = false
42 | view.alpha = 0.6
43 | return view
44 | }()
45 |
46 | override init(frame: CGRect) {
47 | super.init(frame: frame)
48 | setupDesign()
49 | }
50 |
51 | func setupDesign() {
52 |
53 | addSubview(imageHolderView)
54 | imageHolderView.topAnchor.constraint(equalTo: topAnchor , constant: 0).isActive = true
55 | imageHolderView.bottomAnchor.constraint(equalTo: bottomAnchor , constant: 0).isActive = true
56 | imageHolderView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0).isActive = true
57 | imageHolderView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive = true
58 | imageHolderView.layer.cornerRadius = 5
59 | imageHolderView.clipsToBounds = true
60 |
61 |
62 | imageHolderView.addSubview(pageImageView1)
63 | pageImageView1.topAnchor.constraint(equalTo: imageHolderView.topAnchor).isActive = true
64 | pageImageView1.bottomAnchor.constraint(equalTo: imageHolderView.bottomAnchor , constant: 0).isActive = true
65 | pageImageView1.leftAnchor.constraint(equalTo: imageHolderView.leftAnchor).isActive = true
66 | pageImageView1.rightAnchor.constraint(equalTo: imageHolderView.rightAnchor).isActive = true
67 |
68 | }
69 |
70 |
71 | override func layoutSubviews() {
72 | super.layoutSubviews()
73 | imageHolderView.clipsToBounds = true
74 | imageHolderView.layer.cornerRadius = 3
75 | imageHolderView.makeBoundsDropShadow(shadowOffset: .zero, opacity: 0.2, radius: 3)
76 | }
77 |
78 | private func choosePageAsActive() {
79 | UIView.animate(withDuration: 0.1, animations: {
80 | self.imageHolderView.transform = CGAffineTransform(translationX: 0, y: -2.5)
81 | self.imageHolderView.makeBoundsDropShadow(shadowOffset: .zero, opacity: 0.7, radius: 3)
82 | self.imageHolderView.alpha = 1
83 | }) { (_) in
84 | }
85 | }
86 |
87 | private func choosePageAsDiactive() {
88 | UIView.animate(withDuration: 0.1, animations: {
89 | self.imageHolderView.transform = CGAffineTransform(translationX: 0, y: 0)
90 | self.imageHolderView.makeBoundsDropShadow(shadowOffset: .zero, opacity: 0.2, radius: 3)
91 | self.imageHolderView.alpha = 0.6
92 | }) { (_) in
93 | }
94 |
95 | }
96 |
97 | required init?(coder aDecoder: NSCoder) {
98 | fatalError("init(coder:) has not been implemented")
99 | }
100 |
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Book Reader/ReaderPageControllerExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // bookPageControllerExtensions.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 1/24/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | //MARK: book Pages Setup
13 |
14 | extension BookReaderVC {
15 |
16 |
17 | func setupPageController(pageMode: ReaderPageMode) {
18 | bookPageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
19 | bookPageViewController.delegate = self
20 | bookPageViewController.dataSource = self
21 |
22 | addChild(bookPageViewController)
23 | view.addSubview(bookPageViewController.view)
24 |
25 | configureBookPages(pageMode: pageMode)
26 | }
27 |
28 |
29 | func configureBookPages(pageMode: ReaderPageMode) {
30 |
31 | bookPages.removeAll()
32 |
33 | switch pageMode {
34 | case .single:
35 | for bookImage in bookSingleImages {
36 | let bookPage = BookPage()
37 | bookPage.pageNumber = bookSingleImages.firstIndex(where: { $0 == bookImage })
38 | bookPage.image1 = bookImage
39 |
40 | bookPages.append(bookPage)
41 | }
42 | case .double:
43 | for bookImages in bookDoubleImages {
44 | let bookPage = BookPage()
45 |
46 | bookPage.pageNumber = bookDoubleImages.firstIndex(where: { $0 == bookImages })
47 | bookPage.image1 = bookImages.0
48 | bookPage.image2 = bookImages.1
49 |
50 | bookPages.append(bookPage)
51 | }
52 | }
53 |
54 |
55 | }
56 |
57 | // func doublePageIndexForPage(withNumber pageNumber:Int) -> Int? {
58 | // if pageNumber < bookSingleImages.count {
59 | // let number = bookSingleImages[pageNumber].pageNumber
60 | //
61 | // return bookDoubleImages.firstIndex { touple -> Bool in
62 | // return (touple.0?.pageNumber == number || touple.1?.pageNumber == number)
63 | // }
64 | //
65 | // }
66 | // return nil
67 | // }
68 |
69 | }
70 |
71 | //MARK:- book Pages Delegates
72 |
73 | extension BookReaderVC : UIPageViewControllerDataSource , UIPageViewControllerDelegate {
74 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController:
75 | UIViewController) -> UIViewController? {
76 |
77 | if let index = bookPages.firstIndex(of: viewController as! BookPage) {
78 | if index < bookPages.count - 1 {
79 | return bookPages[index + 1]
80 |
81 | }else{
82 | return nil
83 | }
84 | }
85 | return nil
86 | }
87 |
88 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
89 | if let index = bookPages.firstIndex(of: viewController as! BookPage) {
90 | if index > 0 {
91 | return bookPages[index - 1]
92 | }else {
93 | return nil
94 | }
95 | }
96 | return nil
97 | }
98 |
99 |
100 | func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
101 | // setLastViewedPage(toPageWithNumber: pendingPages.first?.image1?.pageNumber ?? 0)
102 |
103 | // print("--------- started transition with view size : (\(page.view.bounds.size)")
104 |
105 |
106 | }
107 |
108 | func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
109 |
110 | if completed {
111 | let currentPage = pageViewController.viewControllers?.first as! BookPage
112 | if let pageNumber1 = currentPage.image1?.pageNumber{
113 | setLastViewedPage(toPageWithNumber: pageNumber1, withAnimate: true)
114 | }else if let pageNumber2 = currentPage.image2?.pageNumber {
115 | setLastViewedPage(toPageWithNumber: pageNumber2, withAnimate: true)
116 | }
117 |
118 | }
119 |
120 | }
121 |
122 |
123 |
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/DynamicConstraintViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RotatableView.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 3/8/1401 AP.
6 | // Copyright © 1401 AP wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | //TODO: Dont set constraint on landscape or not landscape
13 | class DynamicConstraintViewController: UIViewController {
14 |
15 | private var sharedConstaints: [NSLayoutConstraint] = []
16 | private var CVCHConstaints: [NSLayoutConstraint] = []
17 | private var RVRHConstaints: [NSLayoutConstraint] = []
18 | private var CVRHConstaints: [NSLayoutConstraint] = []
19 | private var RVCHConstaints: [NSLayoutConstraint] = []
20 | private var landscapeConstaints: [NSLayoutConstraint] = []
21 | private var portraitConstraints: [NSLayoutConstraint] = []
22 |
23 | final func setupDynamicLayout() {
24 | let traitCollection = UITraitCollection.current
25 | let horizontal = traitCollection.horizontalSizeClass
26 | let vertical = traitCollection.verticalSizeClass
27 |
28 | NSLayoutConstraint.deactivate(CVCHConstaints)
29 | NSLayoutConstraint.deactivate(CVRHConstaints)
30 | NSLayoutConstraint.deactivate(RVCHConstaints)
31 | NSLayoutConstraint.deactivate(RVRHConstaints)
32 | NSLayoutConstraint.deactivate(landscapeConstaints)
33 | NSLayoutConstraint.deactivate(portraitConstraints)
34 | NSLayoutConstraint.deactivate(sharedConstaints)
35 |
36 | NSLayoutConstraint.activate(sharedConstaints)
37 |
38 | if horizontal == .compact && vertical == .compact {
39 | NSLayoutConstraint.activate(CVCHConstaints)
40 | }else if horizontal == .compact && vertical == .regular {
41 | NSLayoutConstraint.activate(RVCHConstaints)
42 | }else if horizontal == .regular && vertical == .compact {
43 | NSLayoutConstraint.activate(CVRHConstaints)
44 | }else {
45 | NSLayoutConstraint.activate(RVRHConstaints)
46 | }
47 |
48 | if UIDevice.current.orientation.isLandscape {
49 | NSLayoutConstraint.activate(landscapeConstaints)
50 | }else {
51 | NSLayoutConstraint.activate(portraitConstraints)
52 | }
53 |
54 | }
55 |
56 | final func setConstraints(shared: [NSLayoutConstraint]) {
57 | self.sharedConstaints = shared
58 | }
59 |
60 | final func setConstraints(CVCH: [NSLayoutConstraint] = [], RVCH: [NSLayoutConstraint] = [], CVRH: [NSLayoutConstraint] = [], RVRH: [NSLayoutConstraint] = []) {
61 | self.CVCHConstaints = CVCH
62 | self.CVRHConstaints = CVRH
63 | self.RVCHConstaints = RVCH
64 | self.RVRHConstaints = RVRH
65 | }
66 |
67 | final func setConstraints(portrait: [NSLayoutConstraint] = [], landscape: [NSLayoutConstraint]) {
68 | self.portraitConstraints = portrait
69 | self.landscapeConstaints = landscape
70 | }
71 |
72 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
73 | super.traitCollectionDidChange(previousTraitCollection)
74 | setupDynamicLayout()
75 |
76 | }
77 |
78 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
79 | super.viewWillTransition(to: size, with: coordinator)
80 | //because in ipad traitCollectionDidChange would not work
81 | setupDynamicLayout()
82 | }
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Info/InfoVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfoVC.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 3/7/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import MessageUI
12 | import StoreKit
13 | import Combine
14 |
15 | class InfoVC: UITableViewController {
16 |
17 | @IBOutlet weak var mailImageView: UIImageView!
18 | @IBOutlet weak var githubImageView: UIImageView!
19 | @IBOutlet weak var rateImageView: UIImageView!
20 | @IBOutlet weak var versionLabel: UILabel!
21 | @IBOutlet weak var iconImage: UIImageView!
22 | @IBOutlet weak var comicNameSwitch: UISwitch!
23 | private var cancellables = Set()
24 |
25 |
26 | override func viewDidLoad() {
27 |
28 | super.viewDidLoad()
29 | setupDesign()
30 | setVersionLabel()
31 | setupComicNameSwitch()
32 | }
33 |
34 | func setupDesign(){
35 |
36 | let githubImage = UIImage(named: "github")?.withRenderingMode(.alwaysTemplate)
37 | githubImageView.tintColor = .appMainColor
38 | githubImageView.image = githubImage
39 |
40 | let emailImage = UIImage(named: "mail")?.withRenderingMode(.alwaysTemplate)
41 | mailImageView.tintColor = .appMainColor
42 | mailImageView.image = emailImage
43 |
44 | let rateImage = UIImage(named: "smile")?.withRenderingMode(.alwaysTemplate)
45 | rateImageView.tintColor = .appMainColor
46 | rateImageView.image = rateImage
47 |
48 | iconImage.makeDropShadow(shadowOffset: .zero, opacity: 0.5, radius: 3)
49 |
50 | navigationController?.navigationBar.tintColor = .appMainColor
51 |
52 |
53 | }
54 |
55 | func setVersionLabel() {
56 | if let versionText = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
57 | versionLabel.text = "version: " + versionText
58 | }
59 | }
60 |
61 | func setupComicNameSwitch() {
62 | AppState.main.$showComicNames.replaceNil(with: false)
63 | .weakAssign(to: \.isOn, on: comicNameSwitch)
64 | .store(in: &cancellables)
65 | }
66 |
67 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
68 | if indexPath.section == 2 {
69 | if indexPath.row == 0 { emailCellTapped() }
70 | if indexPath.row == 1 { githubCellTapped() }
71 | if indexPath.row == 2 { rateCellTapped() }
72 | }
73 | }
74 |
75 | override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
76 | indexPath.section != 0
77 | }
78 |
79 | func emailCellTapped() {
80 | if MFMailComposeViewController.canSendMail() {
81 | let email = MFMailComposeViewController()
82 | email.mailComposeDelegate = self
83 | email.setToRecipients(["shaysugg@protonmail.com"])
84 |
85 | present(email, animated: true)
86 | }
87 |
88 | }
89 |
90 | func githubCellTapped() {
91 | guard let url = URL(string: "https://github.com/shaysugg/ComicKhan") else { return }
92 | UIApplication.shared.open(url)
93 | }
94 |
95 | func rateCellTapped() {
96 | if let url = URL(string: "itms-apps://itunes.apple.com/app/" + "id1516810943") {
97 | UIApplication.shared.open(url, options: [:], completionHandler: nil)
98 | }
99 | }
100 |
101 | @IBAction func comicNameSwitchDidTapped(_ sender: Any) {
102 | AppState.main.setShouldShowComicNames(to: comicNameSwitch.isOn)
103 | }
104 | }
105 |
106 | extension InfoVC: MFMailComposeViewControllerDelegate {
107 | func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
108 | self.dismiss(animated: true, completion: nil)
109 | }
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Library/Extensions/Library+CollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LibraryCollectionViewExtensions.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 3/2/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 |
13 | extension LibraryVC : UICollectionViewDelegate , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout {
14 |
15 |
16 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
17 | fetchResultController.sections?[section].objects?.count ?? 0
18 | }
19 |
20 |
21 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
22 |
23 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectioViewIDs.comicCell.id, for: indexPath) as! LibraryCell
24 | cell.isInEditingMode = editingMode
25 | cell.book = fetchResultController.object(at: indexPath)
26 | cell.showNameLabel = AppState.main.showComicNames
27 | return cell
28 | }
29 |
30 |
31 | func numberOfSections(in collectionView: UICollectionView) -> Int {
32 | fetchResultController.sections?.count ?? 0
33 | }
34 |
35 |
36 |
37 | func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
38 |
39 | let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CollectioViewIDs.comicGroupHeader.id, for: indexPath) as! LibraryReusableView
40 |
41 | header.headerLabel.text = fetchResultController.sections?[indexPath.section].name
42 | return header
43 | }
44 |
45 |
46 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
47 | return libraryCellSize
48 | }
49 |
50 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
51 | let comic = fetchResultController.sections?[section].objects?.first as! Comic
52 | let isGroupForNewComics = comic.ofComicGroup?.isForNewComics ?? false
53 |
54 | if isGroupForNewComics {
55 | return CGSize(width: collectionView.bounds.width, height: 10)
56 | }else {
57 | return CGSize(width: collectionView.bounds.width, height: 50)
58 | }
59 |
60 | }
61 |
62 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
63 |
64 | let selectedComic = fetchResultController.object(at: indexPath)
65 |
66 | if editingMode {
67 | indexSelectionManager.insert(indexPath)
68 |
69 | }else{
70 |
71 | collectionView.selectItem(at: nil, animated: false, scrollPosition: [])
72 |
73 | var readerVCFactory = BookReaderFactory(comic: selectedComic)
74 | readerVCFactory.loadingHandler = { [weak self] in
75 | self?.addLoadingView()
76 | }
77 |
78 | readerVCFactory.comicReadingProgressDidChanged = {[weak self] comic , lastPage in
79 | guard let self = self else { return }
80 | guard let index = self.fetchResultController.indexPath(forObject: comic) else { return }
81 | try? self.dataService.saveLastPageOf(comic: comic, lastPage: lastPage)
82 | self.bookCollectionView.reloadItems(at: [index])
83 | }
84 |
85 | readerVCFactory.build { [weak self] readerVC in
86 | self?.present(readerVC, animated: true, completion: { [weak self] in
87 |
88 | self?.removeLoadingView()
89 | })
90 | }
91 | }
92 | }
93 |
94 | func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
95 |
96 | if editingMode{
97 | indexSelectionManager.remove(indexPath)
98 | }
99 | }
100 |
101 | //MARK:- Cell Animations
102 |
103 |
104 | fileprivate func addLoadingView() {
105 |
106 | let loadingView = LoadingView()
107 | loadingView.translatesAutoresizingMaskIntoConstraints = false
108 | loadingView.tag = 110
109 | loadingView.isHidden = false
110 |
111 | bookCollectionView.addSubview(loadingView)
112 | loadingView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
113 | loadingView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
114 | loadingView.widthAnchor.constraint(equalToConstant: 50).isActive = true
115 | loadingView.heightAnchor.constraint(equalToConstant: 50).isActive = true
116 |
117 | }
118 |
119 | fileprivate func removeLoadingView() {
120 | let view = bookCollectionView.viewWithTag(110)
121 | view?.isHidden = true
122 | view?.removeFromSuperview()
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Library/Extensions/Library+DocumentBrowser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Library+DocumentBrowser.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 5/3/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension LibraryVC: UIDocumentPickerDelegate {
13 | func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
14 |
15 | do{
16 | try appfileManager.moveFilesToUserDiractory(urls: urls)
17 | }catch let err {
18 | showAlert(with: "Oh! A problem happend while moving your files. Please try again.",
19 | description: " \(err.localizedDescription)")
20 | removeProgressView()
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Library/Extensions/Library+EmptyViewDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HowToDelegate.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 3/15/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 |
12 | extension LibraryVC: EmptyViewDelegate {
13 | func copyrightButtonDidTapped() {
14 | showAlert(with: "Zelfportret met hand aan snor (1917)", description: "By Samuel Jessurun de Mesquita (Dutch, 1868 – 1944)\nThe Artist died in 1944 so this work is in the public domain in its country of origin and other countries where the copyright term is the Artist's life plus 70 years or fewer.\n Source: artvee.com")
15 |
16 |
17 | }
18 |
19 | func importComicsButtonTapped() {
20 | presentDocumentPicker()
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Library/Extensions/Library+ExtractionErrorHandeling.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Library+ExtractionErrorHandeling.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 2/15/21.
6 | // Copyright © 2021 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension LibraryVC: ExtractorErrorDelegate, AppFileManagerErrorDelegate {
13 | func errorsAccured(errors: [ComicExteractor.ExtractorError]) {
14 |
15 | var messages = [String]()
16 |
17 | for error in errors {
18 | switch error.type {
19 | case .unsupportFileFormat:
20 | messages.append("Unvalid File Format: cant extract \(error.fileName), only .cbz, .cbr, .pdf is supported.")
21 |
22 | case .comicAlreadyExtracted:
23 | messages.append("File Already Exists: Comic with name of \(error.fileName) already exists.")
24 |
25 | case .createExtractionDirectories:
26 | messages.append("Can't create a directory for \(error.fileName)")
27 |
28 | case .comicExtracting(let extractingError):
29 | switch extractingError {
30 | case .unzipCBZ:
31 | messages.append("Can't unzip \(error.fileName)")
32 | case .unrarCBR:
33 | messages.append("Can't unarchive \(error.fileName)")
34 | case .convertPDFtoImages:
35 | messages.append("Can't convert \(error.fileName) pages to images")
36 |
37 | }
38 |
39 | }
40 | }
41 |
42 | newComicsErrorsDescription = configureDescription(of: messages)
43 | }
44 |
45 | func errorsAccured(errors: [AppFileManager.AppFileManagerError]) {
46 |
47 | var messages = [String]()
48 |
49 | for error in errors {
50 | //comic file has an extraction problem and it probably gonna has a filemanagerError
51 | //we already add its extraction error, better not to add its filemanager error.
52 | if let comicName = error.comicName,
53 | newComicsErrorsDescription.contains(comicName) { continue }
54 |
55 |
56 | switch error.type {
57 | case .cantWriteOnCoreData:
58 | messages.append("Can't write \(error.comicName!) on database.")
59 |
60 | case .cantReadComicDirectory:
61 | messages.append("Can't read extracted comics directory info.")
62 |
63 | case .emptyFile:
64 | messages.append("\(error.comicName!) has no supported images.")
65 | }
66 | }
67 |
68 | newComicsErrorsDescription += configureDescription(of: messages)
69 | }
70 |
71 | func configureDescription(of array: [String]) -> String {
72 | var description = ""
73 | for message in array {
74 | description += "- " + message + "\n"
75 | }
76 | return description
77 | }
78 |
79 | func showExtractionErrorsIfExist() {
80 | if !newComicsErrorsDescription.isEmpty {
81 | showAlert(with: "Can't extract some of your comic files.",
82 | description: newComicsErrorsDescription)
83 |
84 | newComicsErrorsDescription = ""
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Library/Extensions/Library+FetchRsultHandlerDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Library+FetchRsultHandlerDelegate.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 3/6/1401 AP.
6 | // Copyright © 1401 AP wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension LibraryVC: LibraryFetchResultControllerHandlerDelegate {
12 | func libraryBecame(empty: Bool) {
13 | UIView.animate(withDuration: 0.2) { [weak self] in
14 | self?.emptyGroupsView.alpha = empty ? 1 : 0
15 | self?.bookCollectionView.alpha = empty ? 0 : 1
16 | } completion: { [weak self] _ in
17 | self?.emptyGroupsView.isHidden = !empty
18 | self?.bookCollectionView.isHidden = empty
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Library/Extensions/Library+ProgressContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // progressView.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 2/16/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension LibraryVC {
13 |
14 | fileprivate func showProgressView() {
15 | makeBarButtons(hidden: true)
16 |
17 | progressContainer.isHidden = false
18 | progressContainerHideBottomConstrait.isActive = false
19 | progressContainerAppearedBottomConstrait.isActive = true
20 |
21 | progressContainer.spinner.startAnimating()
22 |
23 | UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.35, initialSpringVelocity: 10, options: .curveEaseOut, animations: {
24 | self.view.layoutSubviews()
25 | }, completion: nil)
26 |
27 |
28 | }
29 |
30 | func setUpProgressBarDesign(){
31 | view.addSubview(progressContainer)
32 |
33 | RHConstratis.append(
34 | progressContainer.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.6))
35 | CHConstratis.append(
36 | progressContainer.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9))
37 |
38 | progressContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
39 |
40 | progressContainerHideBottomConstrait = progressContainer.topAnchor.constraint(equalTo: view.bottomAnchor, constant: -10)
41 | progressContainerAppearedBottomConstrait = progressContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10)
42 |
43 |
44 | progressContainerHideBottomConstrait.isActive = true
45 | }
46 |
47 |
48 | fileprivate func redesignForExtractingState() {
49 | UIView.animate(withDuration: 0.3) {
50 | self.view.layoutIfNeeded()
51 | }
52 | }
53 |
54 |
55 | func removeProgressView() {
56 |
57 | progressContainerAppearedBottomConstrait.isActive = false
58 | progressContainerHideBottomConstrait.isActive = true
59 |
60 | UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: {
61 | self.view.layoutSubviews()
62 | }, completion: { _ in
63 | self.progressContainer.isHidden = true
64 | self.progressContainer.setProgress(to: 0)
65 | self.makeBarButtons(hidden: false)
66 | self.progressContainer.spinner.stopAnimating()
67 |
68 | })
69 | editingMode = false
70 | }
71 |
72 |
73 | fileprivate func makeBarButtons(hidden: Bool) {
74 | infoButton.isEnabled = !hidden
75 | addComicsButton.isEnabled = !hidden
76 | editBarButton.isEnabled = !hidden
77 | }
78 |
79 |
80 | }
81 |
82 | extension LibraryVC: ProgressDelegate {
83 | func newFileAboutToCopy(withName name: String) {
84 | showProgressView()
85 | progressContainer.makeProgressBarFor(state: .copying, animated: false)
86 | progressContainer.setTitleLabel(to: "Copying Files ...")
87 | progressContainer.setNumberLabel(to: "10/100")
88 | progressContainer.setProgress(to: 20)
89 | }
90 |
91 |
92 | func newFileAboutToExtract(withName name: String, andNumber number: Int, inTotalFilesCount: Int?) {
93 | DispatchQueue.main.async { [weak self] in
94 |
95 | ///this is quick fix should consider a betterway later
96 | if self?.progressContainerHideBottomConstrait.isActive ?? false {
97 | self?.showProgressView()
98 | }
99 | self?.redesignForExtractingState()
100 | self?.progressContainer.makeProgressBarFor(state: .extracting, animated: true)
101 | self?.progressContainer.setTitleLabel(to: "Extracting: \(name)")
102 | self?.comicNameThatExtracting = name
103 | self?.progressContainer.setProgress(to: 0)
104 | if let totalNumber = inTotalFilesCount {
105 | self?.progressContainer.setNumberLabel(to: String(number) + "/" + String(totalNumber))
106 | }
107 |
108 | }
109 |
110 |
111 | }
112 |
113 | func percentChanged(to value: Double) {
114 | DispatchQueue.main.async { [weak self] in
115 | self?.progressContainer.setProgress(to: CGFloat(value))
116 | }
117 | }
118 |
119 | func extractingProcessFinished() {
120 |
121 | DispatchQueue.main.async {[weak self] in
122 | self?.comicNameThatExtracting = nil
123 | self?.removeProgressView()
124 | }
125 |
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Library/LibraryFetchResultControllerHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Library+FetchResultController.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 4/26/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreData
11 |
12 | class LibraryFetchResultControllerHandler: NSObject, NSFetchedResultsControllerDelegate {
13 |
14 | let collectionView: UICollectionView
15 | let fetchResultController: NSFetchedResultsController
16 | private var blockOperations = [BlockOperation]()
17 | weak var delegate: LibraryFetchResultControllerHandlerDelegate?
18 |
19 | init(fetchResultController: NSFetchedResultsController ,collectionView: UICollectionView) {
20 | self.collectionView = collectionView
21 | self.fetchResultController = fetchResultController
22 | super.init()
23 | fetchResultController.delegate = self
24 | }
25 |
26 | func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
27 |
28 | switch type {
29 | case .insert:
30 | if let index = newIndexPath {
31 | blockOperations.append(BlockOperation(block: { [weak self] in
32 | self?.collectionView.insertItems(at: [index])
33 | }))
34 | }
35 | case .delete:
36 | if let index = indexPath {
37 | blockOperations.append(BlockOperation(block: { [weak self] in
38 | self?.collectionView.deleteItems(at: [index])
39 | }))
40 | }
41 | case .update:
42 | if let updatatdComic = anObject as? Comic,
43 | let index = indexPath {
44 | blockOperations.append(BlockOperation(block: { [weak self] in
45 | let cell = self?.collectionView.cellForItem(at: index) as? LibraryCell
46 | cell?.book = updatatdComic
47 | }))
48 | }
49 | case .move:
50 | if let index = indexPath,
51 | let newIndex = newIndexPath {
52 | blockOperations.append(BlockOperation(block: { [weak self] in
53 | self?.collectionView.deleteItems(at: [index])
54 | self?.collectionView.insertItems(at: [newIndex])
55 | }))
56 | }
57 | default:
58 | break
59 | }
60 |
61 |
62 | delegate?.libraryBecame(empty: controller.fetchedObjects?.isEmpty ?? true)
63 |
64 |
65 | }
66 |
67 |
68 | func controller(_ controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
69 | sectionName
70 | }
71 |
72 | func controller(_ controller: NSFetchedResultsController, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
73 |
74 | let indexSet = IndexSet(arrayLiteral: sectionIndex)
75 |
76 | switch type {
77 | case .insert:
78 |
79 | blockOperations.append(BlockOperation(block: { [weak self] in
80 | self?.collectionView.insertSections(indexSet)
81 | }))
82 | print("------FRC Section Insert")
83 | case .delete:
84 | blockOperations.append(BlockOperation(block: { [weak self] in
85 | self?.collectionView.deleteSections(indexSet)
86 | }))
87 | print("------FRC Section Delete")
88 | case .move:
89 | blockOperations.append(BlockOperation(block: { [weak self] in
90 | self?.collectionView.reloadData()
91 | }))
92 | print("------FRC Section Move")
93 | case .update:
94 | blockOperations.append(BlockOperation(block: { [weak self] in
95 | self?.collectionView.reloadSections(indexSet)
96 | }))
97 | print("------FRC Section Update")
98 |
99 | default:
100 | break
101 | }
102 |
103 | }
104 |
105 |
106 | func controllerDidChangeContent(_ controller: NSFetchedResultsController) {
107 |
108 | collectionView.performBatchUpdates({
109 | for operation in blockOperations {
110 | operation.start()
111 | }
112 | }) { (_) in
113 | self.blockOperations.removeAll()
114 | }
115 | }
116 | }
117 |
118 |
119 | protocol LibraryFetchResultControllerHandlerDelegate: NSObject {
120 | func libraryBecame(empty: Bool)
121 | }
122 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Library/Views/LibraryCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookCell.swift
3 | // wutComicReader
4 | //
5 | // Created by Shayan on 7/12/19.
6 | // Copyright © 2019 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class LibraryCell: UICollectionViewCell {
12 |
13 |
14 |
15 | @IBOutlet weak var selectionImageView: UIImageView!
16 | @IBOutlet weak var whiteView: UIView!
17 | @IBOutlet weak var coverImageView: UIImageView!
18 | @IBOutlet weak var readProgressView: CircleProgressView!
19 | @IBOutlet weak var nameLabel: UILabel!
20 | @IBOutlet weak var shadowView: UIView!
21 |
22 | var book : Comic? {
23 | didSet{
24 | guard let imageName = book?.thumbnailNames?.firstObject as? String else { return }
25 | let cover = ComicImage(book, withImageName: imageName)
26 | coverImageView.image = UIImage(contentsOfFile: cover.path)
27 | if let name = book?.name {
28 | nameLabel.text = name
29 | }
30 | updateProgressValue()
31 | }
32 | }
33 |
34 | var isInEditingMode: Bool = false {
35 | didSet{
36 | selectionImageView.isHidden = !isInEditingMode
37 | if book?.lastVisitedPage != 0 {
38 | readProgressView.isHidden = isInEditingMode
39 | }
40 | }
41 | }
42 |
43 |
44 | override var isSelected: Bool {
45 | didSet{
46 | if !isInEditingMode { return }
47 | UIView.animate(withDuration: 0.2) {
48 | self.whiteView.alpha = self.isSelected ? 0.6 : 0
49 | }
50 | selectionImageView.image = isSelected ? #imageLiteral(resourceName: "ic-actions-selected").withTintColor(.white) : #imageLiteral(resourceName: "ic-actions-select").withTintColor(.white)
51 | }
52 | }
53 |
54 | var showNameLabel: Bool = true {
55 | didSet {
56 | nameLabel.isHidden = !showNameLabel
57 | }
58 | }
59 |
60 | func setUpDesign(){
61 | selectionImageView.image = #imageLiteral(resourceName: "ic-actions-select").withTintColor(.white)
62 | readProgressView.strokeWidth = 2
63 | coverImageView.layer.cornerRadius = 4
64 | coverImageView.clipsToBounds = true
65 | whiteView.layer.cornerRadius = 4
66 | whiteView.clipsToBounds = true
67 | self.clipsToBounds = false
68 | }
69 |
70 | override func awakeFromNib() {
71 | super.awakeFromNib()
72 | setUpDesign()
73 | updateProgressValue()
74 | }
75 |
76 | override func layoutSubviews() {
77 | super.layoutSubviews()
78 | readProgressView.progressCircleColor = UIColor.appMainColor.cgColor
79 | shadowView.makeDropShadow(shadowOffset: .zero, opacity: 0.5, radius: 5)
80 | }
81 |
82 |
83 | func updateProgressValue(){
84 |
85 | if let lastPage = book?.lastVisitedPage,
86 | let totalPages = book?.imageNames?.count,
87 | lastPage != 0,
88 | totalPages > 1 {
89 | let value: Double = Double(lastPage - 1) / Double(totalPages - 1)
90 | readProgressView.progressValue = CGFloat(value)
91 | readProgressView.isHidden = isInEditingMode
92 | }
93 | if let lastPage = book?.lastVisitedPage,
94 | lastPage == 0 {
95 | readProgressView.isHidden = true
96 | }
97 |
98 | }
99 |
100 |
101 |
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Library/Views/LibraryReusableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LibraryReusableView.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 2/9/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 |
12 | class LibraryReusableView: UICollectionReusableView {
13 |
14 | @IBOutlet weak var headerLabel: UILabel!
15 |
16 |
17 | override func awakeFromNib() {
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Library/Views/LoadingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoadingView.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 5/3/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class LoadingView: UIView {
12 |
13 | lazy var loadingSpinner: UIActivityIndicatorView = {
14 | let spinner = UIActivityIndicatorView(style: .large)
15 | spinner.tintColor = .white
16 | spinner.translatesAutoresizingMaskIntoConstraints = false
17 | return spinner
18 | }()
19 |
20 | lazy private var backgroundView: UIView = {
21 | let view = UIView()
22 | view.backgroundColor = UIColor.appMainColor.withAlphaComponent(0.85)
23 | view.translatesAutoresizingMaskIntoConstraints = false
24 | view.clipsToBounds = true
25 | return view
26 | }()
27 |
28 | override init(frame: CGRect) {
29 | super.init(frame: frame)
30 |
31 |
32 | addSubview(backgroundView)
33 | backgroundView.widthAnchor.constraint(equalToConstant: 50).isActive = true
34 | backgroundView.heightAnchor.constraint(equalToConstant: 50).isActive = true
35 | backgroundView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
36 | backgroundView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
37 |
38 | backgroundView.addSubview(loadingSpinner)
39 | loadingSpinner.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
40 | loadingSpinner.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
41 | loadingSpinner.widthAnchor.constraint(equalToConstant: 50).isActive = true
42 | loadingSpinner.heightAnchor.constraint(equalToConstant: 50).isActive = true
43 |
44 | backgroundView.layer.cornerRadius = 25
45 | loadingSpinner.startAnimating()
46 |
47 | }
48 |
49 | required init?(coder: NSCoder) {
50 | super.init(coder: coder)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/Library/Views/ProgressContainerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // progressContainerView.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 3/2/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | class ProgressContainerView: UIView {
13 |
14 | enum State {
15 | case copying
16 | case extracting
17 | }
18 |
19 | private lazy var nameLabel: UILabel = {
20 | let label = UILabel()
21 | label.font = AppState.main.font.body
22 | label.textAlignment = .center
23 | label.textColor = .white
24 | label.translatesAutoresizingMaskIntoConstraints = false
25 | return label
26 | }()
27 |
28 | private var numberLabel: UILabel = {
29 | let label = UILabel()
30 | label.font = AppState.main.font.body
31 | label.textColor = .white
32 | label.textAlignment = .center
33 | label.translatesAutoresizingMaskIntoConstraints = false
34 | return label
35 | }()
36 |
37 | private lazy var hStackView: UIStackView = {
38 | let stackView = UIStackView()
39 | stackView.axis = .horizontal
40 | stackView.alignment = .center
41 | stackView.distribution = .fill
42 | stackView.spacing = 10
43 | stackView.translatesAutoresizingMaskIntoConstraints = false
44 | return stackView
45 | }()
46 |
47 | private lazy var vStackView: UIStackView = {
48 | let stackView = UIStackView()
49 | stackView.axis = .vertical
50 | stackView.alignment = .fill
51 | stackView.distribution = .fill
52 | stackView.spacing = 10
53 | stackView.translatesAutoresizingMaskIntoConstraints = false
54 | return stackView
55 | }()
56 |
57 | private var progressView: RoundedProgressView = {
58 | let progressView = RoundedProgressView()
59 | progressView.translatesAutoresizingMaskIntoConstraints = false
60 | progressView.progressViewTint = .appProgressColor
61 | progressView.trackViewTint = .appMainSecondary
62 | return progressView
63 | }()
64 |
65 | lazy var spinner: UIActivityIndicatorView = {
66 | let view = UIActivityIndicatorView()
67 | view.color = .white
68 | view.hidesWhenStopped = true
69 | view.translatesAutoresizingMaskIntoConstraints = false
70 | return view
71 | }()
72 |
73 |
74 | override init(frame: CGRect) {
75 | super.init(frame: frame)
76 | setUpDesign()
77 | }
78 |
79 | required init?(coder: NSCoder) {
80 | super.init(coder: coder)
81 | setUpDesign()
82 | }
83 |
84 |
85 |
86 | private func setUpDesign() {
87 |
88 | backgroundColor = .appMainColor
89 | clipsToBounds = true
90 |
91 | addSubview(vStackView)
92 | vStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
93 | vStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive = true
94 | vStackView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
95 | vStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10).isActive = true
96 |
97 |
98 | hStackView.addArrangedSubview(nameLabel)
99 | hStackView.addArrangedSubview(numberLabel)
100 | hStackView.addArrangedSubview(spinner)
101 |
102 | vStackView.addArrangedSubview(hStackView)
103 | vStackView.addArrangedSubview(progressView)
104 |
105 | progressView.heightAnchor.constraint(equalToConstant: 15).isActive = true
106 |
107 |
108 |
109 | }
110 |
111 | override func layoutSubviews() {
112 | super.layoutSubviews()
113 | layer.cornerRadius = bounds.height * 0.3
114 | self.makeBoundsDropShadow(shadowOffset: .zero, opacity: 0.3, radius: 10)
115 | }
116 |
117 | func setProgress(to value: CGFloat) {
118 | progressView.setProgress(to: value, animated: true)
119 | }
120 |
121 | func setTitleLabel(to string: String) {
122 | nameLabel.text = string
123 | }
124 |
125 | func setNumberLabel(to string: String) {
126 | numberLabel.text = string
127 | }
128 |
129 | func makeProgressBarFor(state: State, animated: Bool) {
130 |
131 | if state == .copying {
132 | progressView.removeFromSuperview()
133 |
134 | }else {
135 | vStackView.addArrangedSubview(progressView)
136 | }
137 |
138 |
139 | if animated {
140 | UIView.animate(withDuration: 0.3) {
141 | self.progressView.alpha = state == .copying ? 0 : 1
142 | self.layoutIfNeeded()
143 | }
144 | }
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/wutComicReader/ViewControllers/New Comic Group/NewGroupVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // newGroupVC.swift
3 | // wutComicReader
4 | //
5 | // Created by Sha Yan on 2/8/20.
6 | // Copyright © 2020 wutup. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreData
11 |
12 |
13 | class NewGroupVC: UIViewController {
14 |
15 | var dataService: DataService!
16 | var comicsAboutToGroup: [Comic] = []
17 |
18 | var groups: [ComicGroup]! { didSet {
19 | groupNames = groups.map({
20 | $0.name ?? ""
21 | })
22 | } }
23 | var groupNames: [String]!
24 |
25 | var newComicGroupAboutToAdd: ((_ name: String, _ comics: [Comic]) -> Void)?
26 | var comicsGroupAboutToMove: ((_ group: ComicGroup, _ comics: [Comic]) -> Void)?
27 |
28 | @IBOutlet weak var alreadyExistLabel: UILabel!
29 | @IBOutlet weak var newGroupTextField: UITextField!
30 | @IBOutlet weak var addLabel: UILabel!
31 | @IBOutlet weak var groupTableView: UITableView!
32 | @IBOutlet var addButton: UIButton!
33 |
34 |
35 | @IBAction func addGroupButtonTapped(_ sender: Any) {
36 | addButtonTapped()
37 | }
38 |
39 | @IBAction func cancelButtonTapped(_ sender: Any) {
40 | dismiss(animated: true, completion: nil)
41 | }
42 | override func viewDidLoad() {
43 | super.viewDidLoad()
44 |
45 | do {
46 | try dataService.deleteEmptyGroups()
47 | groups = try dataService.fetchComicGroups()
48 | }catch{
49 | groups = []
50 | }
51 |
52 | setUpDesign()
53 |
54 | groupTableView.delegate = self
55 | groupTableView.dataSource = self
56 | newGroupTextField.delegate = self
57 |
58 | newGroupTextField.becomeFirstResponder()
59 |
60 | addButton.isEnabled = false
61 | addButton.alpha = 0.5
62 | }
63 |
64 | private func setUpDesign(){
65 | newGroupTextField.clipsToBounds = true
66 | newGroupTextField.layer.cornerRadius = newGroupTextField.bounds.height * 0.25
67 | addButton.clipsToBounds = true
68 | addButton.layer.cornerRadius = 10
69 |
70 | let rect = CGRect(x: 0, y: 0, width: newGroupTextField.bounds.height * 0.5 , height: 50)
71 | newGroupTextField.leftView = UIView(frame: rect)
72 | newGroupTextField.leftViewMode = .always
73 |
74 | groupTableView.tableFooterView = UIView()
75 |
76 |
77 |
78 | }
79 |
80 |
81 | private func addButtonTapped(){
82 | if let text = newGroupTextField.text,
83 | !text.isEmpty,
84 | !groupNames.contains(text) {
85 | newComicGroupAboutToAdd?(text, comicsAboutToGroup)
86 | dismiss(animated: true, completion: nil)
87 | }else {
88 | alreadyExistLabel.isHidden = false
89 | }
90 | }
91 |
92 | }
93 |
94 | extension NewGroupVC: UITableViewDelegate , UITableViewDataSource {
95 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
96 | groups.count
97 | }
98 |
99 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
100 | let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath)
101 | if groups[indexPath.row].isForNewComics {
102 | cell.textLabel?.text = "Untitled"
103 | }else {
104 | cell.textLabel?.text = groups[indexPath.row].name
105 | }
106 | return cell
107 | }
108 |
109 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
110 | comicsGroupAboutToMove?(groups[indexPath.row], comicsAboutToGroup)
111 | dismiss(animated: true, completion: nil)
112 | }
113 |
114 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
115 | return 55
116 | }
117 |
118 |
119 | }
120 |
121 |
122 | extension NewGroupVC: UITextFieldDelegate {
123 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
124 | addButtonTapped()
125 | return true
126 | }
127 |
128 |
129 | func textFieldDidChangeSelection(_ textField: UITextField) {
130 | if let text = textField.text, !text.isEmpty {
131 | addButton.isEnabled = true
132 | addButton.alpha = 1
133 | }else {
134 | addButton.isEnabled = false
135 | addButton.alpha = 0.5
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------