├── .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 | --------------------------------------------------------------------------------