├── Plans.md ├── README.md ├── Xenon Reader.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── hkamran.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── Xenon Reader ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json └── DefaultCover.imageset │ ├── Contents.json │ ├── Placeholder.png │ ├── Placeholder@2x.png │ └── Placeholder@3x.png ├── ContentView.swift ├── Extensions ├── EPUBDocumentExtensions.swift └── StringExtensions.swift ├── Helpers ├── EPUBHelpers.swift ├── FilesystemHelpers.swift ├── Helpers.swift ├── LibraryHelpers.swift ├── ReadableHelpers.swift ├── Realm │ ├── RealmHelpers.swift │ └── RealmObjects.swift └── View Helpers │ ├── LibraryBookModifier.swift │ ├── TOCFocusModifier.swift │ ├── ToolbarModifier.swift │ └── ViewHelpers.swift ├── Info.plist ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── Views ├── Library │ ├── AuthorsView.swift │ ├── Library View Types │ │ ├── GridView.swift │ │ ├── GridViewBook.swift │ │ ├── ListView.swift │ │ └── ListViewBook.swift │ ├── LibraryParentView.swift │ ├── LibraryView.swift │ ├── PublishersView.swift │ └── Sub-views │ │ ├── ReadableInformation.swift │ │ └── SidebarView.swift ├── Reader │ ├── ReaderParentView.swift │ ├── ReaderRenderView.swift │ ├── ReaderSidebarView.swift │ └── Sub-views │ │ ├── ReaderSidebarMetadata.swift │ │ └── ReaderSidebarTOC.swift ├── Settings │ ├── DebugSettingsView.swift │ ├── LibrarySettingsView.swift │ ├── ReaderSettingsView.swift │ └── SettingsView.swift ├── Sub-views │ ├── DetailListRow.swift │ ├── FileWebView.swift │ └── NewCategorySheet.swift └── Testing │ ├── FileListView.swift │ ├── TReaderView.swift │ └── VariablesView.swift ├── Xenon_Reader.entitlements └── Xenon_ReaderApp.swift /Plans.md: -------------------------------------------------------------------------------- 1 | # Plans 2 | 3 | ## Onboarding 4 | - Walk through setup process 5 | - Select library path or create library folder in ~/Documents/Xenon Library 6 | 7 | ## Library 8 | - Search 9 | - Subtitle changes (readables in each section) 10 | - Example 11 | - Library: 100 readables 12 | - Authors: 100 readables 13 | - Selected author: 5 readables 14 | - Publishers: 100 readables 15 | - Selected publisher: 1 readable 16 | - Categories 17 | - Add Realm back when categories are added 18 | 19 | ## Reader 20 | - Pagination 21 | - Loading of external content 22 | - Stylesheets 23 | - Images 24 | - External link viewing (view in browser) 25 | - Bookmarks 26 | - Highlights (multicolor) 27 | - Notes (multicolor) 28 | - Search 29 | 30 | ## Settings 31 | - Library 32 | - Library folder 33 | - Library view 34 | - Library sort 35 | - Application theme (use macOS-style graphics) 36 | - Folder list 37 | - Reader 38 | - Font 39 | - Font size 40 | - Line spacing 41 | - Line width 42 | - Margin 43 | - Text alignment 44 | - Background color (macOS-style graphics, same visuals as app theme) 45 | - Set unzipped directory to ~/Library/Application Support/bundle.id 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xenon Reader 2 | ### A new EPUB reader for macOS, built with the latest technologies 3 | 4 | ![Platform: macOS 11.0+](https://img.shields.io/badge/platform-macos%2011.0%2B-blue?style=for-the-badge) 5 | ![Language: Swift](https://img.shields.io/badge/language-Swift-FA7343?style=for-the-badge) 6 | -------------------------------------------------------------------------------- /Xenon Reader.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1200346725E3A45D00F467F1 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1200346625E3A45D00F467F1 /* LibraryView.swift */; }; 11 | 1200347425E3C2CE00F467F1 /* NewCategorySheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1200347325E3C2CE00F467F1 /* NewCategorySheet.swift */; }; 12 | 1203E25225DCBCE0001D999E /* ToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1203E25125DCBCE0001D999E /* ToolbarModifier.swift */; }; 13 | 1203E26625DCE0EB001D999E /* EPUBDocumentExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1203E26525DCE0EB001D999E /* EPUBDocumentExtensions.swift */; }; 14 | 1220F94625E63FAC0077BEC8 /* LibraryParentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1220F94525E63FAC0077BEC8 /* LibraryParentView.swift */; }; 15 | 1220F94925E640120077BEC8 /* ReaderParentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1220F94825E640120077BEC8 /* ReaderParentView.swift */; }; 16 | 1220F95525E641A70077BEC8 /* ReaderSidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1220F95425E641A70077BEC8 /* ReaderSidebarView.swift */; }; 17 | 1220F95B25E650C10077BEC8 /* ReaderRenderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1220F95A25E650C10077BEC8 /* ReaderRenderView.swift */; }; 18 | 122529D325E8ACB5006184DB /* ReadableHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 122529D225E8ACB5006184DB /* ReadableHelpers.swift */; }; 19 | 122529D625E8ACF8006184DB /* LibraryHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 122529D525E8ACF8006184DB /* LibraryHelpers.swift */; }; 20 | 122529DE25E8B64B006184DB /* LibraryBookModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 122529DD25E8B64B006184DB /* LibraryBookModifier.swift */; }; 21 | 1239425525DBCAAB00B408DB /* Xenon_ReaderApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1239425425DBCAAB00B408DB /* Xenon_ReaderApp.swift */; }; 22 | 1239425725DBCAAB00B408DB /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1239425625DBCAAB00B408DB /* ContentView.swift */; }; 23 | 1239425925DBCAAD00B408DB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1239425825DBCAAD00B408DB /* Assets.xcassets */; }; 24 | 1239425C25DBCAAD00B408DB /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1239425B25DBCAAD00B408DB /* Preview Assets.xcassets */; }; 25 | 1239426825DBCD2900B408DB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1239426725DBCD2900B408DB /* SettingsView.swift */; }; 26 | 1239427525DBD75600B408DB /* EPUBHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1239427425DBD75600B408DB /* EPUBHelpers.swift */; }; 27 | 1239427C25DBDAA700B408DB /* DebugSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1239427B25DBDAA700B408DB /* DebugSettingsView.swift */; }; 28 | 1252059D25DDB01600E6E464 /* RealmObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1252059C25DDB01600E6E464 /* RealmObjects.swift */; }; 29 | 128336C225EA210000EEAEBE /* EPUBKit in Frameworks */ = {isa = PBXBuildFile; productRef = 128336C125EA210000EEAEBE /* EPUBKit */; }; 30 | 12A8A54025DCE7BE003147E2 /* AuthorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A8A53F25DCE7BE003147E2 /* AuthorsView.swift */; }; 31 | 12B4DEEA25DC7C8400B5908F /* GridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B4DEE925DC7C8400B5908F /* GridView.swift */; }; 32 | 12B4DEED25DC7D1800B5908F /* GridViewBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B4DEEC25DC7D1800B5908F /* GridViewBook.swift */; }; 33 | 12DD222525E9962400373A7B /* FileWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12DD222425E9962400373A7B /* FileWebView.swift */; }; 34 | 12ECB5C825FB346200BBD6E3 /* ReaderSidebarTOC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12ECB5C725FB346200BBD6E3 /* ReaderSidebarTOC.swift */; }; 35 | 12ECB5CD25FB36C100BBD6E3 /* ReaderSidebarMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12ECB5CC25FB36C100BBD6E3 /* ReaderSidebarMetadata.swift */; }; 36 | 12ECB5D025FB374B00BBD6E3 /* DetailListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12ECB5CF25FB374B00BBD6E3 /* DetailListRow.swift */; }; 37 | 12ECB5D325FBD6EB00BBD6E3 /* TOCFocusModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12ECB5D225FBD6EB00BBD6E3 /* TOCFocusModifier.swift */; }; 38 | 12ECB5D625FBDCD600BBD6E3 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12ECB5D525FBDCD600BBD6E3 /* StringExtensions.swift */; }; 39 | 12EDE4D725E31656005110E2 /* ReadableInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EDE4D625E31656005110E2 /* ReadableInformation.swift */; }; 40 | 12F22C4425E43704001BB4AE /* PublishersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F22C4325E43704001BB4AE /* PublishersView.swift */; }; 41 | 12F22C4725E43852001BB4AE /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F22C4625E43852001BB4AE /* ListView.swift */; }; 42 | 12F22C4A25E43907001BB4AE /* ListViewBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F22C4925E43907001BB4AE /* ListViewBook.swift */; }; 43 | 12F4FC3625F87299005ACFB5 /* ReaderSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F4FC3525F87299005ACFB5 /* ReaderSettingsView.swift */; }; 44 | 12F4FC3925F872B2005ACFB5 /* LibrarySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F4FC3825F872B2005ACFB5 /* LibrarySettingsView.swift */; }; 45 | 12F5355125E0BD9500D11F49 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F5355025E0BD9500D11F49 /* Helpers.swift */; }; 46 | 12F5355825E0BEC500D11F49 /* FileListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F5355725E0BEC500D11F49 /* FileListView.swift */; }; 47 | 12F5355C25E0EB5500D11F49 /* RealmHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F5355B25E0EB5500D11F49 /* RealmHelpers.swift */; }; 48 | 12F5356425E1FEF500D11F49 /* VariablesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F5356325E1FEF500D11F49 /* VariablesView.swift */; }; 49 | 12F5356725E20C5B00D11F49 /* ViewHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F5356625E20C5B00D11F49 /* ViewHelpers.swift */; }; 50 | 12F5356B25E223AC00D11F49 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F5356A25E223AC00D11F49 /* SidebarView.swift */; }; 51 | 12F6F7C525E8637600FAB635 /* FilesystemHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F6F7C425E8637600FAB635 /* FilesystemHelpers.swift */; }; 52 | /* End PBXBuildFile section */ 53 | 54 | /* Begin PBXFileReference section */ 55 | 1200346625E3A45D00F467F1 /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = ""; }; 56 | 1200347325E3C2CE00F467F1 /* NewCategorySheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewCategorySheet.swift; sourceTree = ""; }; 57 | 1203E25125DCBCE0001D999E /* ToolbarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarModifier.swift; sourceTree = ""; }; 58 | 1203E26125DCDD86001D999E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 59 | 1203E26525DCE0EB001D999E /* EPUBDocumentExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPUBDocumentExtensions.swift; sourceTree = ""; }; 60 | 1220F94525E63FAC0077BEC8 /* LibraryParentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryParentView.swift; sourceTree = ""; }; 61 | 1220F94825E640120077BEC8 /* ReaderParentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderParentView.swift; sourceTree = ""; }; 62 | 1220F95425E641A70077BEC8 /* ReaderSidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderSidebarView.swift; sourceTree = ""; }; 63 | 1220F95A25E650C10077BEC8 /* ReaderRenderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderRenderView.swift; sourceTree = ""; }; 64 | 122529D225E8ACB5006184DB /* ReadableHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadableHelpers.swift; sourceTree = ""; }; 65 | 122529D525E8ACF8006184DB /* LibraryHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryHelpers.swift; sourceTree = ""; }; 66 | 122529DD25E8B64B006184DB /* LibraryBookModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryBookModifier.swift; sourceTree = ""; }; 67 | 1239425125DBCAAB00B408DB /* Xenon Reader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Xenon Reader.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | 1239425425DBCAAB00B408DB /* Xenon_ReaderApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xenon_ReaderApp.swift; sourceTree = ""; }; 69 | 1239425625DBCAAB00B408DB /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 70 | 1239425825DBCAAD00B408DB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 71 | 1239425B25DBCAAD00B408DB /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 72 | 1239425D25DBCAAD00B408DB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73 | 1239425E25DBCAAD00B408DB /* Xenon_Reader.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Xenon_Reader.entitlements; sourceTree = ""; }; 74 | 1239426725DBCD2900B408DB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 75 | 1239427425DBD75600B408DB /* EPUBHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPUBHelpers.swift; sourceTree = ""; }; 76 | 1239427B25DBDAA700B408DB /* DebugSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugSettingsView.swift; sourceTree = ""; }; 77 | 1252059C25DDB01600E6E464 /* RealmObjects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmObjects.swift; sourceTree = ""; }; 78 | 12A8A53F25DCE7BE003147E2 /* AuthorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorsView.swift; sourceTree = ""; }; 79 | 12B4DEE925DC7C8400B5908F /* GridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridView.swift; sourceTree = ""; }; 80 | 12B4DEEC25DC7D1800B5908F /* GridViewBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridViewBook.swift; sourceTree = ""; }; 81 | 12DD222425E9962400373A7B /* FileWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileWebView.swift; sourceTree = ""; }; 82 | 12ECB5C725FB346200BBD6E3 /* ReaderSidebarTOC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderSidebarTOC.swift; sourceTree = ""; }; 83 | 12ECB5CC25FB36C100BBD6E3 /* ReaderSidebarMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderSidebarMetadata.swift; sourceTree = ""; }; 84 | 12ECB5CF25FB374B00BBD6E3 /* DetailListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailListRow.swift; sourceTree = ""; }; 85 | 12ECB5D225FBD6EB00BBD6E3 /* TOCFocusModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOCFocusModifier.swift; sourceTree = ""; }; 86 | 12ECB5D525FBDCD600BBD6E3 /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; 87 | 12EDE4D625E31656005110E2 /* ReadableInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadableInformation.swift; sourceTree = ""; }; 88 | 12F22C4325E43704001BB4AE /* PublishersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishersView.swift; sourceTree = ""; }; 89 | 12F22C4625E43852001BB4AE /* ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListView.swift; sourceTree = ""; }; 90 | 12F22C4925E43907001BB4AE /* ListViewBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewBook.swift; sourceTree = ""; }; 91 | 12F4FC3325F815B0005ACFB5 /* Plans.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Plans.md; sourceTree = SOURCE_ROOT; }; 92 | 12F4FC3525F87299005ACFB5 /* ReaderSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderSettingsView.swift; sourceTree = ""; }; 93 | 12F4FC3825F872B2005ACFB5 /* LibrarySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySettingsView.swift; sourceTree = ""; }; 94 | 12F5355025E0BD9500D11F49 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Helpers.swift; path = "Xenon Reader/Helpers/Helpers.swift"; sourceTree = SOURCE_ROOT; }; 95 | 12F5355725E0BEC500D11F49 /* FileListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileListView.swift; sourceTree = ""; }; 96 | 12F5355B25E0EB5500D11F49 /* RealmHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmHelpers.swift; sourceTree = ""; }; 97 | 12F5356325E1FEF500D11F49 /* VariablesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariablesView.swift; sourceTree = ""; }; 98 | 12F5356625E20C5B00D11F49 /* ViewHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewHelpers.swift; sourceTree = ""; }; 99 | 12F5356A25E223AC00D11F49 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; }; 100 | 12F6F7C425E8637600FAB635 /* FilesystemHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesystemHelpers.swift; sourceTree = ""; }; 101 | /* End PBXFileReference section */ 102 | 103 | /* Begin PBXFrameworksBuildPhase section */ 104 | 1239424E25DBCAAB00B408DB /* Frameworks */ = { 105 | isa = PBXFrameworksBuildPhase; 106 | buildActionMask = 2147483647; 107 | files = ( 108 | 128336C225EA210000EEAEBE /* EPUBKit in Frameworks */, 109 | ); 110 | runOnlyForDeploymentPostprocessing = 0; 111 | }; 112 | /* End PBXFrameworksBuildPhase section */ 113 | 114 | /* Begin PBXGroup section */ 115 | 1200346925E3A70F00F467F1 /* Sub-views */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 12ECB5CF25FB374B00BBD6E3 /* DetailListRow.swift */, 119 | 1200347325E3C2CE00F467F1 /* NewCategorySheet.swift */, 120 | 12DD222425E9962400373A7B /* FileWebView.swift */, 121 | ); 122 | path = "Sub-views"; 123 | sourceTree = ""; 124 | }; 125 | 1200346D25E3A73600F467F1 /* Testing */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 12F5356325E1FEF500D11F49 /* VariablesView.swift */, 129 | 12F5355725E0BEC500D11F49 /* FileListView.swift */, 130 | ); 131 | path = Testing; 132 | sourceTree = ""; 133 | }; 134 | 1203E25425DCBD9F001D999E /* View Helpers */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 12ECB5D225FBD6EB00BBD6E3 /* TOCFocusModifier.swift */, 138 | 122529DD25E8B64B006184DB /* LibraryBookModifier.swift */, 139 | 1203E25125DCBCE0001D999E /* ToolbarModifier.swift */, 140 | 12F5356625E20C5B00D11F49 /* ViewHelpers.swift */, 141 | ); 142 | path = "View Helpers"; 143 | sourceTree = ""; 144 | }; 145 | 1203E25525DCBDA9001D999E /* Helpers */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 12F5355025E0BD9500D11F49 /* Helpers.swift */, 149 | 122529D525E8ACF8006184DB /* LibraryHelpers.swift */, 150 | 122529D225E8ACB5006184DB /* ReadableHelpers.swift */, 151 | 1239427425DBD75600B408DB /* EPUBHelpers.swift */, 152 | 12F6F7C425E8637600FAB635 /* FilesystemHelpers.swift */, 153 | 1252059F25DDB01A00E6E464 /* Realm */, 154 | 1203E25425DCBD9F001D999E /* View Helpers */, 155 | ); 156 | path = Helpers; 157 | sourceTree = ""; 158 | }; 159 | 1203E26825DCE18E001D999E /* Extensions */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | 1203E26525DCE0EB001D999E /* EPUBDocumentExtensions.swift */, 163 | 12ECB5D525FBDCD600BBD6E3 /* StringExtensions.swift */, 164 | ); 165 | path = Extensions; 166 | sourceTree = ""; 167 | }; 168 | 1220F94B25E640B60077BEC8 /* Library View Types */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 12B4DEE925DC7C8400B5908F /* GridView.swift */, 172 | 12F22C4625E43852001BB4AE /* ListView.swift */, 173 | 12F22C4925E43907001BB4AE /* ListViewBook.swift */, 174 | 12B4DEEC25DC7D1800B5908F /* GridViewBook.swift */, 175 | ); 176 | path = "Library View Types"; 177 | sourceTree = ""; 178 | }; 179 | 1220F94D25E6412E0077BEC8 /* Library */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 1220F94525E63FAC0077BEC8 /* LibraryParentView.swift */, 183 | 1200346625E3A45D00F467F1 /* LibraryView.swift */, 184 | 12A8A53F25DCE7BE003147E2 /* AuthorsView.swift */, 185 | 12F22C4325E43704001BB4AE /* PublishersView.swift */, 186 | 1220F94B25E640B60077BEC8 /* Library View Types */, 187 | 1220F95125E6414C0077BEC8 /* Sub-views */, 188 | ); 189 | path = Library; 190 | sourceTree = ""; 191 | }; 192 | 1220F94F25E641360077BEC8 /* Reader */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | 1220F94825E640120077BEC8 /* ReaderParentView.swift */, 196 | 1220F95A25E650C10077BEC8 /* ReaderRenderView.swift */, 197 | 1220F95425E641A70077BEC8 /* ReaderSidebarView.swift */, 198 | 12ECB5CA25FB346500BBD6E3 /* Sub-views */, 199 | ); 200 | path = Reader; 201 | sourceTree = ""; 202 | }; 203 | 1220F95125E6414C0077BEC8 /* Sub-views */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | 12EDE4D625E31656005110E2 /* ReadableInformation.swift */, 207 | 12F5356A25E223AC00D11F49 /* SidebarView.swift */, 208 | ); 209 | path = "Sub-views"; 210 | sourceTree = ""; 211 | }; 212 | 1239424825DBCAAB00B408DB = { 213 | isa = PBXGroup; 214 | children = ( 215 | 12F4FC3325F815B0005ACFB5 /* Plans.md */, 216 | 1239425325DBCAAB00B408DB /* Xenon Reader */, 217 | 1239425225DBCAAB00B408DB /* Products */, 218 | ); 219 | sourceTree = ""; 220 | }; 221 | 1239425225DBCAAB00B408DB /* Products */ = { 222 | isa = PBXGroup; 223 | children = ( 224 | 1239425125DBCAAB00B408DB /* Xenon Reader.app */, 225 | ); 226 | name = Products; 227 | sourceTree = ""; 228 | }; 229 | 1239425325DBCAAB00B408DB /* Xenon Reader */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | 1239425425DBCAAB00B408DB /* Xenon_ReaderApp.swift */, 233 | 1239425625DBCAAB00B408DB /* ContentView.swift */, 234 | 1203E26125DCDD86001D999E /* README.md */, 235 | 12F5355525E0BEA600D11F49 /* Views */, 236 | 1203E25525DCBDA9001D999E /* Helpers */, 237 | 1203E26825DCE18E001D999E /* Extensions */, 238 | 1239425D25DBCAAD00B408DB /* Info.plist */, 239 | 1239425825DBCAAD00B408DB /* Assets.xcassets */, 240 | 1239425E25DBCAAD00B408DB /* Xenon_Reader.entitlements */, 241 | 1239425A25DBCAAD00B408DB /* Preview Content */, 242 | ); 243 | path = "Xenon Reader"; 244 | sourceTree = ""; 245 | }; 246 | 1239425A25DBCAAD00B408DB /* Preview Content */ = { 247 | isa = PBXGroup; 248 | children = ( 249 | 1239425B25DBCAAD00B408DB /* Preview Assets.xcassets */, 250 | ); 251 | path = "Preview Content"; 252 | sourceTree = ""; 253 | }; 254 | 1252059F25DDB01A00E6E464 /* Realm */ = { 255 | isa = PBXGroup; 256 | children = ( 257 | 12F5355B25E0EB5500D11F49 /* RealmHelpers.swift */, 258 | 1252059C25DDB01600E6E464 /* RealmObjects.swift */, 259 | ); 260 | path = Realm; 261 | sourceTree = ""; 262 | }; 263 | 12B4DEE325DC7A2000B5908F /* Settings */ = { 264 | isa = PBXGroup; 265 | children = ( 266 | 1239426725DBCD2900B408DB /* SettingsView.swift */, 267 | 12F4FC3825F872B2005ACFB5 /* LibrarySettingsView.swift */, 268 | 12F4FC3525F87299005ACFB5 /* ReaderSettingsView.swift */, 269 | 1239427B25DBDAA700B408DB /* DebugSettingsView.swift */, 270 | ); 271 | path = Settings; 272 | sourceTree = ""; 273 | }; 274 | 12ECB5CA25FB346500BBD6E3 /* Sub-views */ = { 275 | isa = PBXGroup; 276 | children = ( 277 | 12ECB5CC25FB36C100BBD6E3 /* ReaderSidebarMetadata.swift */, 278 | 12ECB5C725FB346200BBD6E3 /* ReaderSidebarTOC.swift */, 279 | ); 280 | path = "Sub-views"; 281 | sourceTree = ""; 282 | }; 283 | 12F5355525E0BEA600D11F49 /* Views */ = { 284 | isa = PBXGroup; 285 | children = ( 286 | 1220F94F25E641360077BEC8 /* Reader */, 287 | 1220F94D25E6412E0077BEC8 /* Library */, 288 | 1200346D25E3A73600F467F1 /* Testing */, 289 | 1200346925E3A70F00F467F1 /* Sub-views */, 290 | 12B4DEE325DC7A2000B5908F /* Settings */, 291 | ); 292 | path = Views; 293 | sourceTree = ""; 294 | }; 295 | /* End PBXGroup section */ 296 | 297 | /* Begin PBXNativeTarget section */ 298 | 1239425025DBCAAB00B408DB /* Xenon Reader */ = { 299 | isa = PBXNativeTarget; 300 | buildConfigurationList = 1239426125DBCAAD00B408DB /* Build configuration list for PBXNativeTarget "Xenon Reader" */; 301 | buildPhases = ( 302 | 1239424D25DBCAAB00B408DB /* Sources */, 303 | 1239424E25DBCAAB00B408DB /* Frameworks */, 304 | 1239424F25DBCAAB00B408DB /* Resources */, 305 | ); 306 | buildRules = ( 307 | ); 308 | dependencies = ( 309 | ); 310 | name = "Xenon Reader"; 311 | packageProductDependencies = ( 312 | 128336C125EA210000EEAEBE /* EPUBKit */, 313 | ); 314 | productName = "Xenon Reader"; 315 | productReference = 1239425125DBCAAB00B408DB /* Xenon Reader.app */; 316 | productType = "com.apple.product-type.application"; 317 | }; 318 | /* End PBXNativeTarget section */ 319 | 320 | /* Begin PBXProject section */ 321 | 1239424925DBCAAB00B408DB /* Project object */ = { 322 | isa = PBXProject; 323 | attributes = { 324 | LastSwiftUpdateCheck = 1240; 325 | LastUpgradeCheck = 1240; 326 | TargetAttributes = { 327 | 1239425025DBCAAB00B408DB = { 328 | CreatedOnToolsVersion = 12.4; 329 | }; 330 | }; 331 | }; 332 | buildConfigurationList = 1239424C25DBCAAB00B408DB /* Build configuration list for PBXProject "Xenon Reader" */; 333 | compatibilityVersion = "Xcode 9.3"; 334 | developmentRegion = en; 335 | hasScannedForEncodings = 0; 336 | knownRegions = ( 337 | en, 338 | Base, 339 | ); 340 | mainGroup = 1239424825DBCAAB00B408DB; 341 | packageReferences = ( 342 | 128336C025EA210000EEAEBE /* XCRemoteSwiftPackageReference "EPUBKit" */, 343 | ); 344 | productRefGroup = 1239425225DBCAAB00B408DB /* Products */; 345 | projectDirPath = ""; 346 | projectRoot = ""; 347 | targets = ( 348 | 1239425025DBCAAB00B408DB /* Xenon Reader */, 349 | ); 350 | }; 351 | /* End PBXProject section */ 352 | 353 | /* Begin PBXResourcesBuildPhase section */ 354 | 1239424F25DBCAAB00B408DB /* Resources */ = { 355 | isa = PBXResourcesBuildPhase; 356 | buildActionMask = 2147483647; 357 | files = ( 358 | 1239425C25DBCAAD00B408DB /* Preview Assets.xcassets in Resources */, 359 | 1239425925DBCAAD00B408DB /* Assets.xcassets in Resources */, 360 | ); 361 | runOnlyForDeploymentPostprocessing = 0; 362 | }; 363 | /* End PBXResourcesBuildPhase section */ 364 | 365 | /* Begin PBXSourcesBuildPhase section */ 366 | 1239424D25DBCAAB00B408DB /* Sources */ = { 367 | isa = PBXSourcesBuildPhase; 368 | buildActionMask = 2147483647; 369 | files = ( 370 | 1239426825DBCD2900B408DB /* SettingsView.swift in Sources */, 371 | 1200346725E3A45D00F467F1 /* LibraryView.swift in Sources */, 372 | 12DD222525E9962400373A7B /* FileWebView.swift in Sources */, 373 | 12ECB5D025FB374B00BBD6E3 /* DetailListRow.swift in Sources */, 374 | 12B4DEED25DC7D1800B5908F /* GridViewBook.swift in Sources */, 375 | 12F5355825E0BEC500D11F49 /* FileListView.swift in Sources */, 376 | 12F4FC3925F872B2005ACFB5 /* LibrarySettingsView.swift in Sources */, 377 | 122529D325E8ACB5006184DB /* ReadableHelpers.swift in Sources */, 378 | 1239427525DBD75600B408DB /* EPUBHelpers.swift in Sources */, 379 | 12EDE4D725E31656005110E2 /* ReadableInformation.swift in Sources */, 380 | 12ECB5C825FB346200BBD6E3 /* ReaderSidebarTOC.swift in Sources */, 381 | 12A8A54025DCE7BE003147E2 /* AuthorsView.swift in Sources */, 382 | 122529DE25E8B64B006184DB /* LibraryBookModifier.swift in Sources */, 383 | 12F6F7C525E8637600FAB635 /* FilesystemHelpers.swift in Sources */, 384 | 12F22C4425E43704001BB4AE /* PublishersView.swift in Sources */, 385 | 122529D625E8ACF8006184DB /* LibraryHelpers.swift in Sources */, 386 | 1220F94925E640120077BEC8 /* ReaderParentView.swift in Sources */, 387 | 12ECB5CD25FB36C100BBD6E3 /* ReaderSidebarMetadata.swift in Sources */, 388 | 1200347425E3C2CE00F467F1 /* NewCategorySheet.swift in Sources */, 389 | 12B4DEEA25DC7C8400B5908F /* GridView.swift in Sources */, 390 | 12F22C4725E43852001BB4AE /* ListView.swift in Sources */, 391 | 1203E26625DCE0EB001D999E /* EPUBDocumentExtensions.swift in Sources */, 392 | 1220F94625E63FAC0077BEC8 /* LibraryParentView.swift in Sources */, 393 | 12F5356425E1FEF500D11F49 /* VariablesView.swift in Sources */, 394 | 12ECB5D625FBDCD600BBD6E3 /* StringExtensions.swift in Sources */, 395 | 12F5356725E20C5B00D11F49 /* ViewHelpers.swift in Sources */, 396 | 1252059D25DDB01600E6E464 /* RealmObjects.swift in Sources */, 397 | 12F5355125E0BD9500D11F49 /* Helpers.swift in Sources */, 398 | 1220F95525E641A70077BEC8 /* ReaderSidebarView.swift in Sources */, 399 | 1239425725DBCAAB00B408DB /* ContentView.swift in Sources */, 400 | 1239425525DBCAAB00B408DB /* Xenon_ReaderApp.swift in Sources */, 401 | 12F5355C25E0EB5500D11F49 /* RealmHelpers.swift in Sources */, 402 | 12F5356B25E223AC00D11F49 /* SidebarView.swift in Sources */, 403 | 1203E25225DCBCE0001D999E /* ToolbarModifier.swift in Sources */, 404 | 12F22C4A25E43907001BB4AE /* ListViewBook.swift in Sources */, 405 | 12ECB5D325FBD6EB00BBD6E3 /* TOCFocusModifier.swift in Sources */, 406 | 1239427C25DBDAA700B408DB /* DebugSettingsView.swift in Sources */, 407 | 12F4FC3625F87299005ACFB5 /* ReaderSettingsView.swift in Sources */, 408 | 1220F95B25E650C10077BEC8 /* ReaderRenderView.swift in Sources */, 409 | ); 410 | runOnlyForDeploymentPostprocessing = 0; 411 | }; 412 | /* End PBXSourcesBuildPhase section */ 413 | 414 | /* Begin XCBuildConfiguration section */ 415 | 1239425F25DBCAAD00B408DB /* Debug */ = { 416 | isa = XCBuildConfiguration; 417 | buildSettings = { 418 | ALWAYS_SEARCH_USER_PATHS = NO; 419 | CLANG_ANALYZER_NONNULL = YES; 420 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 421 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 422 | CLANG_CXX_LIBRARY = "libc++"; 423 | CLANG_ENABLE_MODULES = YES; 424 | CLANG_ENABLE_OBJC_ARC = YES; 425 | CLANG_ENABLE_OBJC_WEAK = YES; 426 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 427 | CLANG_WARN_BOOL_CONVERSION = YES; 428 | CLANG_WARN_COMMA = YES; 429 | CLANG_WARN_CONSTANT_CONVERSION = YES; 430 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 431 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 432 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 433 | CLANG_WARN_EMPTY_BODY = YES; 434 | CLANG_WARN_ENUM_CONVERSION = YES; 435 | CLANG_WARN_INFINITE_RECURSION = YES; 436 | CLANG_WARN_INT_CONVERSION = YES; 437 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 438 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 439 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 440 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 441 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 442 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 443 | CLANG_WARN_STRICT_PROTOTYPES = YES; 444 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 445 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 446 | CLANG_WARN_UNREACHABLE_CODE = YES; 447 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 448 | COPY_PHASE_STRIP = NO; 449 | DEBUG_INFORMATION_FORMAT = dwarf; 450 | ENABLE_STRICT_OBJC_MSGSEND = YES; 451 | ENABLE_TESTABILITY = YES; 452 | GCC_C_LANGUAGE_STANDARD = gnu11; 453 | GCC_DYNAMIC_NO_PIC = NO; 454 | GCC_NO_COMMON_BLOCKS = YES; 455 | GCC_OPTIMIZATION_LEVEL = 0; 456 | GCC_PREPROCESSOR_DEFINITIONS = ( 457 | "DEBUG=1", 458 | "$(inherited)", 459 | ); 460 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 461 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 462 | GCC_WARN_UNDECLARED_SELECTOR = YES; 463 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 464 | GCC_WARN_UNUSED_FUNCTION = YES; 465 | GCC_WARN_UNUSED_VARIABLE = YES; 466 | MACOSX_DEPLOYMENT_TARGET = 11.0; 467 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 468 | MTL_FAST_MATH = YES; 469 | ONLY_ACTIVE_ARCH = YES; 470 | SDKROOT = macosx; 471 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 472 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 473 | }; 474 | name = Debug; 475 | }; 476 | 1239426025DBCAAD00B408DB /* Release */ = { 477 | isa = XCBuildConfiguration; 478 | buildSettings = { 479 | ALWAYS_SEARCH_USER_PATHS = NO; 480 | CLANG_ANALYZER_NONNULL = YES; 481 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 482 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 483 | CLANG_CXX_LIBRARY = "libc++"; 484 | CLANG_ENABLE_MODULES = YES; 485 | CLANG_ENABLE_OBJC_ARC = YES; 486 | CLANG_ENABLE_OBJC_WEAK = YES; 487 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 488 | CLANG_WARN_BOOL_CONVERSION = YES; 489 | CLANG_WARN_COMMA = YES; 490 | CLANG_WARN_CONSTANT_CONVERSION = YES; 491 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 492 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 493 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 494 | CLANG_WARN_EMPTY_BODY = YES; 495 | CLANG_WARN_ENUM_CONVERSION = YES; 496 | CLANG_WARN_INFINITE_RECURSION = YES; 497 | CLANG_WARN_INT_CONVERSION = YES; 498 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 499 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 500 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 501 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 502 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 503 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 504 | CLANG_WARN_STRICT_PROTOTYPES = YES; 505 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 506 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 507 | CLANG_WARN_UNREACHABLE_CODE = YES; 508 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 509 | COPY_PHASE_STRIP = NO; 510 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 511 | ENABLE_NS_ASSERTIONS = NO; 512 | ENABLE_STRICT_OBJC_MSGSEND = YES; 513 | GCC_C_LANGUAGE_STANDARD = gnu11; 514 | GCC_NO_COMMON_BLOCKS = YES; 515 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 516 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 517 | GCC_WARN_UNDECLARED_SELECTOR = YES; 518 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 519 | GCC_WARN_UNUSED_FUNCTION = YES; 520 | GCC_WARN_UNUSED_VARIABLE = YES; 521 | MACOSX_DEPLOYMENT_TARGET = 11.0; 522 | MTL_ENABLE_DEBUG_INFO = NO; 523 | MTL_FAST_MATH = YES; 524 | SDKROOT = macosx; 525 | SWIFT_COMPILATION_MODE = wholemodule; 526 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 527 | }; 528 | name = Release; 529 | }; 530 | 1239426225DBCAAD00B408DB /* Debug */ = { 531 | isa = XCBuildConfiguration; 532 | buildSettings = { 533 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 534 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 535 | CODE_SIGN_ENTITLEMENTS = "Xenon Reader/Xenon_Reader.entitlements"; 536 | CODE_SIGN_IDENTITY = "-"; 537 | CODE_SIGN_STYLE = Automatic; 538 | COMBINE_HIDPI_IMAGES = YES; 539 | DEVELOPMENT_ASSET_PATHS = "\"Xenon Reader/Preview Content\""; 540 | DEVELOPMENT_TEAM = ""; 541 | ENABLE_PREVIEWS = YES; 542 | INFOPLIST_FILE = "Xenon Reader/Info.plist"; 543 | LD_RUNPATH_SEARCH_PATHS = ( 544 | "$(inherited)", 545 | "@executable_path/../Frameworks", 546 | ); 547 | MACOSX_DEPLOYMENT_TARGET = 11.0; 548 | MARKETING_VERSION = 0.1; 549 | PRODUCT_BUNDLE_IDENTIFIER = com.hkamran.XenonReader; 550 | PRODUCT_NAME = "$(TARGET_NAME)"; 551 | PROVISIONING_PROFILE_SPECIFIER = ""; 552 | SWIFT_VERSION = 5.0; 553 | }; 554 | name = Debug; 555 | }; 556 | 1239426325DBCAAD00B408DB /* Release */ = { 557 | isa = XCBuildConfiguration; 558 | buildSettings = { 559 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 560 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 561 | CODE_SIGN_ENTITLEMENTS = "Xenon Reader/Xenon_Reader.entitlements"; 562 | CODE_SIGN_IDENTITY = "-"; 563 | CODE_SIGN_STYLE = Automatic; 564 | COMBINE_HIDPI_IMAGES = YES; 565 | DEVELOPMENT_ASSET_PATHS = "\"Xenon Reader/Preview Content\""; 566 | DEVELOPMENT_TEAM = ""; 567 | ENABLE_PREVIEWS = YES; 568 | INFOPLIST_FILE = "Xenon Reader/Info.plist"; 569 | LD_RUNPATH_SEARCH_PATHS = ( 570 | "$(inherited)", 571 | "@executable_path/../Frameworks", 572 | ); 573 | MACOSX_DEPLOYMENT_TARGET = 11.0; 574 | MARKETING_VERSION = 0.1; 575 | PRODUCT_BUNDLE_IDENTIFIER = com.hkamran.XenonReader; 576 | PRODUCT_NAME = "$(TARGET_NAME)"; 577 | PROVISIONING_PROFILE_SPECIFIER = ""; 578 | SWIFT_VERSION = 5.0; 579 | }; 580 | name = Release; 581 | }; 582 | /* End XCBuildConfiguration section */ 583 | 584 | /* Begin XCConfigurationList section */ 585 | 1239424C25DBCAAB00B408DB /* Build configuration list for PBXProject "Xenon Reader" */ = { 586 | isa = XCConfigurationList; 587 | buildConfigurations = ( 588 | 1239425F25DBCAAD00B408DB /* Debug */, 589 | 1239426025DBCAAD00B408DB /* Release */, 590 | ); 591 | defaultConfigurationIsVisible = 0; 592 | defaultConfigurationName = Release; 593 | }; 594 | 1239426125DBCAAD00B408DB /* Build configuration list for PBXNativeTarget "Xenon Reader" */ = { 595 | isa = XCConfigurationList; 596 | buildConfigurations = ( 597 | 1239426225DBCAAD00B408DB /* Debug */, 598 | 1239426325DBCAAD00B408DB /* Release */, 599 | ); 600 | defaultConfigurationIsVisible = 0; 601 | defaultConfigurationName = Release; 602 | }; 603 | /* End XCConfigurationList section */ 604 | 605 | /* Begin XCRemoteSwiftPackageReference section */ 606 | 128336C025EA210000EEAEBE /* XCRemoteSwiftPackageReference "EPUBKit" */ = { 607 | isa = XCRemoteSwiftPackageReference; 608 | repositoryURL = "https://github.com/hkamran80/EPUBKit"; 609 | requirement = { 610 | branch = master; 611 | kind = branch; 612 | }; 613 | }; 614 | /* End XCRemoteSwiftPackageReference section */ 615 | 616 | /* Begin XCSwiftPackageProductDependency section */ 617 | 128336C125EA210000EEAEBE /* EPUBKit */ = { 618 | isa = XCSwiftPackageProductDependency; 619 | package = 128336C025EA210000EEAEBE /* XCRemoteSwiftPackageReference "EPUBKit" */; 620 | productName = EPUBKit; 621 | }; 622 | /* End XCSwiftPackageProductDependency section */ 623 | }; 624 | rootObject = 1239424925DBCAAB00B408DB /* Project object */; 625 | } 626 | -------------------------------------------------------------------------------- /Xenon Reader.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Xenon Reader.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Xenon Reader.xcodeproj/xcuserdata/hkamran.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | GettingStarted (Playground) 1.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 3 13 | 14 | GettingStarted (Playground) 2.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 4 20 | 21 | GettingStarted (Playground) 3.xcscheme 22 | 23 | isShown 24 | 25 | orderHint 26 | 2 27 | 28 | GettingStarted (Playground) 4.xcscheme 29 | 30 | isShown 31 | 32 | orderHint 33 | 5 34 | 35 | GettingStarted (Playground) 5.xcscheme 36 | 37 | isShown 38 | 39 | orderHint 40 | 6 41 | 42 | GettingStarted (Playground).xcscheme 43 | 44 | isShown 45 | 46 | orderHint 47 | 1 48 | 49 | Xenon Reader.xcscheme_^#shared#^_ 50 | 51 | orderHint 52 | 0 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Xenon Reader/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.357", 9 | "green" : "0.094", 10 | "red" : "0.761" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "light" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.357", 27 | "green" : "0.094", 28 | "red" : "0.761" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | }, 33 | { 34 | "appearances" : [ 35 | { 36 | "appearance" : "luminosity", 37 | "value" : "dark" 38 | } 39 | ], 40 | "color" : { 41 | "color-space" : "srgb", 42 | "components" : { 43 | "alpha" : "1.000", 44 | "blue" : "0.573", 45 | "green" : "0.384", 46 | "red" : "0.941" 47 | } 48 | }, 49 | "idiom" : "universal" 50 | } 51 | ], 52 | "info" : { 53 | "author" : "xcode", 54 | "version" : 1 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Xenon Reader/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Xenon Reader/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Xenon Reader/Assets.xcassets/DefaultCover.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Placeholder.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Placeholder@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Placeholder@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Xenon Reader/Assets.xcassets/DefaultCover.imageset/Placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hkamran80/xenon-reader/f3fc2b36330b1be33fa1d55833ac982ac2e629a3/Xenon Reader/Assets.xcassets/DefaultCover.imageset/Placeholder.png -------------------------------------------------------------------------------- /Xenon Reader/Assets.xcassets/DefaultCover.imageset/Placeholder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hkamran80/xenon-reader/f3fc2b36330b1be33fa1d55833ac982ac2e629a3/Xenon Reader/Assets.xcassets/DefaultCover.imageset/Placeholder@2x.png -------------------------------------------------------------------------------- /Xenon Reader/Assets.xcassets/DefaultCover.imageset/Placeholder@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hkamran80/xenon-reader/f3fc2b36330b1be33fa1d55833ac982ac2e629a3/Xenon Reader/Assets.xcassets/DefaultCover.imageset/Placeholder@3x.png -------------------------------------------------------------------------------- /Xenon Reader/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/16/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | // TODO: Add onboarding 12 | struct ContentView: View { 13 | @AppStorage("libraryPath") var libraryPath = "" 14 | @AppStorage("libraryUrl") var libraryUrl = "" 15 | 16 | @EnvironmentObject var xrShared: XRShared 17 | 18 | var body: some View { 19 | NavigationView { 20 | switch self.xrShared.mainViewType { 21 | case .library: LibraryParentView().environmentObject(xrShared) 22 | case .reader: ReaderParentView().environmentObject(xrShared) 23 | } 24 | } 25 | .navigationTitle(self.xrShared.mainViewType == .library ? "Xenon Reader" : (self.xrShared.activeReadable?.epub?.title ?? "Unknown Title")) 26 | .navigationSubtitle(generateSubtitle(xrShared: xrShared)) 27 | .frame(minWidth: 350, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity) 28 | .modifier(ToolbarModifier(xrShared: self.xrShared)) 29 | } 30 | } 31 | 32 | #if DEBUG 33 | struct ContentView_Previews: PreviewProvider { 34 | static var previews: some View { 35 | ContentView() 36 | } 37 | } 38 | #endif 39 | -------------------------------------------------------------------------------- /Xenon Reader/Extensions/EPUBDocumentExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EPUBDocumentExtensions.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/16/21. 6 | // 7 | 8 | import Foundation 9 | import EPUBKit 10 | 11 | extension EPUBDocument: Hashable, Equatable { 12 | public static func == (lhs: EPUBDocument, rhs: EPUBDocument) -> Bool { 13 | return lhs.title == rhs.title && lhs.author == rhs.author && lhs.publisher == rhs.publisher 14 | } 15 | 16 | public func hash(into hasher: inout Hasher) { 17 | hasher.combine(title) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Xenon Reader/Extensions/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringExtensions.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 3/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | func stripOutHtml() -> String? { 12 | do { 13 | guard let data = self.data(using: .unicode) else { 14 | return nil 15 | } 16 | let attributed = try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) 17 | return attributed.string 18 | } catch { 19 | return nil 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Xenon Reader/Helpers/EPUBHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EPUBHelpers.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/16/21. 6 | // 7 | 8 | import Cocoa 9 | import EPUBKit 10 | import Foundation 11 | 12 | // MARK: Wrapper Struct 13 | 14 | struct EpubLoader: Hashable { 15 | let id: String 16 | let epub: EPUBDocument? 17 | let storageLocation: StorageLocation = .applicationSupport 18 | var spineItemIndex: Int = 0 19 | 20 | init(withUrl fileUrl: URL) { 21 | // TODO: Figure out a way of handling readables with duplicate titles 22 | id = fileUrl.lastPathComponent.replacingOccurrences(of: ".epub", with: "").removingPercentEncoding! 23 | switch storageLocation { 24 | case .documents: epub = EPUBDocument(url: fileUrl, extractionDirectory: getAppDocumentsDirectory().appendingPathComponent(id)) 25 | case .applicationSupport: epub = EPUBDocument(url: fileUrl, extractionDirectory: getAppSupportDirectory().appendingPathComponent(id)) 26 | } 27 | } 28 | } 29 | 30 | // MARK: Functions 31 | 32 | func loadImage(_ imagePath: URL?) -> NSImage? { 33 | if let imagePathUrl = imagePath { 34 | do { 35 | let imageData = try Data(contentsOf: imagePathUrl) 36 | return NSImage(data: imageData) 37 | } catch { 38 | print(error.localizedDescription) 39 | return nil 40 | } 41 | } else { 42 | return NSImage(named: "DefaultCover") 43 | } 44 | } 45 | 46 | func getEpubPage(epubFilename: String, path: String) -> Data? { 47 | let pathUrl = getEpubPageUrl(epubFilename: epubFilename, path: path) 48 | 49 | do { 50 | return try Data(contentsOf: pathUrl) 51 | } catch { 52 | print("[P] epubFilename: \(epubFilename)") 53 | print("[P] path: \(path)") 54 | print("[ERROR] \(error.localizedDescription)") 55 | 56 | return nil 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Xenon Reader/Helpers/FilesystemHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilesystemHelpers.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/25/21. 6 | // 7 | 8 | import AEXML 9 | import EPUBKit 10 | import Foundation 11 | 12 | // MARK: Directory List Functions 13 | 14 | // TODO: Be able to search recursively 15 | func retrieveDirectoryList(libraryPath: String, showHidden: Bool = false, fileExtension: String = "") -> [String]? { 16 | if libraryPath != "" { 17 | let fm = FileManager.default 18 | 19 | do { 20 | let items = try fm.contentsOfDirectory(atPath: libraryPath) 21 | let filteredItems = showHidden ? items : items.filter { !$0.hasPrefix(".") } 22 | 23 | return fileExtension != "" ? filteredItems.filter { $0.hasSuffix(fileExtension) } : filteredItems 24 | } catch { 25 | print(error.localizedDescription) 26 | return nil 27 | } 28 | } else { 29 | print("Invalid library path") 30 | return nil 31 | } 32 | } 33 | 34 | // MARK: Creation/Generation Functions 35 | 36 | func absolutePath(libraryPath: String, filename: String) -> String { 37 | if libraryPath.last == "/" { 38 | return libraryPath + filename 39 | } else { 40 | return libraryPath + "/" + filename 41 | } 42 | } 43 | 44 | func createAppDocumentsDirectory() { 45 | let documentsDirectory: URL = getDocumentsDirectory()! 46 | let appDirectory: URL = documentsDirectory.appendingPathComponent("Xenon Reader", isDirectory: true) 47 | 48 | let fm = FileManager.default 49 | 50 | do { 51 | try fm.createDirectory(atPath: appDirectory.path, withIntermediateDirectories: true, attributes: nil) 52 | } catch { 53 | print("An error occurred when creating the application directory: \(error.localizedDescription)") 54 | } 55 | } 56 | 57 | func generateFileUrl(libraryUrl: String, filename: String, fileExtension: String) -> URL? { 58 | return URL(fileURLWithPath: filename, relativeTo: URL(string: libraryUrl)).appendingPathExtension(fileExtension) 59 | } 60 | 61 | // MARK: Fetch Functions 62 | 63 | func getDocumentsDirectory() -> URL? { 64 | return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] 65 | } 66 | 67 | func getApplicationSupportDirectory() -> URL? { 68 | return FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first 69 | } 70 | 71 | func getAppDocumentsDirectory() -> URL { 72 | return getDocumentsDirectory()?.appendingPathComponent("Xenon Reader", isDirectory: true) ?? getDocumentsDirectory()! 73 | } 74 | 75 | func getAppSupportDirectory() -> URL { 76 | return getApplicationSupportDirectory()!.appendingPathComponent(Bundle.main.bundleIdentifier ?? "com.hkamran.XenonReader") 77 | } 78 | 79 | func getEpubDirectory(epubFilename: String, storageLocation: StorageLocation = StorageLocation.applicationSupport) -> URL? { 80 | let storagePath = storageLocation == .documents ? getAppDocumentsDirectory() : getAppSupportDirectory() 81 | return storagePath.appendingPathComponent(epubFilename.replacingOccurrences(of: ".epub", with: "").removingPercentEncoding!, isDirectory: true) 82 | } 83 | 84 | func getEpubPageUrl(epubFilename: String, path: String, storageLocation: StorageLocation = StorageLocation.applicationSupport) -> URL { 85 | let epubDirectory = getEpubPageDirectoryUrl(epubId: epubFilename, storageLocation: storageLocation) 86 | let pathComponents = path.removingPercentEncoding!.components(separatedBy: ".") 87 | 88 | return URL(fileURLWithPath: pathComponents[0], relativeTo: epubDirectory).appendingPathExtension(pathComponents[1]) 89 | } 90 | 91 | //func getEpubPageDirectoryUrl(epubFilename: String, path: String, storageLocation: StorageLocation = StorageLocation.applicationSupport) -> URL { 92 | // let epubDirectory = getEpubDirectory(epubFilename: epubFilename) 93 | // let pathComponents = path.removingPercentEncoding!.components(separatedBy: ".") 94 | // var pathFileComponents = pathComponents[0].components(separatedBy: "/") 95 | // 96 | // pathFileComponents.removeLast() 97 | // 98 | // return URL(fileURLWithPath: pathFileComponents.joined(separator: "/"), isDirectory: true, relativeTo: epubDirectory) 99 | //} 100 | 101 | func getEpubPageDirectoryUrl(epubId: String, storageLocation: StorageLocation) -> URL { 102 | let epubDirectory = getEpubDirectory(epubFilename: epubId, storageLocation: storageLocation) 103 | let epubContainerXML = URL(fileURLWithPath: "container.xml", relativeTo: epubDirectory?.appendingPathComponent("META-INF", isDirectory: true)) 104 | 105 | do { 106 | let data = try Data(contentsOf: epubContainerXML) 107 | let xml = try AEXMLDocument(xml: data) 108 | 109 | let pathComponent = xml.root["rootfiles"]["rootfile"].attributes["full-path"]?.components(separatedBy: "/").first 110 | 111 | if pathComponent?.contains(".") == true { 112 | return epubDirectory! 113 | } else { 114 | return (epubDirectory?.appendingPathComponent(pathComponent!, isDirectory: true))! 115 | } 116 | } catch { 117 | print("[ERROR] getEpubPageDirectoryUrl2: \(error.localizedDescription)") 118 | 119 | return epubDirectory! 120 | } 121 | } 122 | 123 | // MARK: Fetch Function Helpers 124 | 125 | enum StorageLocation { 126 | case documents 127 | case applicationSupport 128 | } 129 | -------------------------------------------------------------------------------- /Xenon Reader/Helpers/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/19/21. 6 | // 7 | 8 | import CryptoKit 9 | import EPUBKit 10 | import Foundation 11 | //import RealmSwift 12 | 13 | class XRShared: ObservableObject { 14 | @Published var epubs: [EpubLoader] = [] 15 | @Published var authors: [Author] = [] 16 | @Published var publishers: [Publisher] = [] 17 | // TODO: Reset to empty array 18 | @Published var categories: [ReadableCategory] = [ReadableCategory(id: UUID().uuidString, name: "First Category", imageName: "tray.circle", creationDate: Date())] 19 | 20 | @Published var mainViewType: MainViewType = .library 21 | @Published var activeReadable: EpubLoader? = nil 22 | 23 | @Published var fileList: [String] = [] 24 | // @Published var realmInstance: Realm? = initializeRealm() 25 | 26 | @Published var categoryCreationSheet: Bool = false 27 | } 28 | 29 | // TODO: Create extension to EPUBDocument which adds the hash as a parameter 30 | func sha256Hash(_ inputData: Data) -> String { 31 | let hashedData = SHA256.hash(data: inputData) 32 | 33 | return hashedData.compactMap { String(format: "%02x", $0) }.joined() 34 | } 35 | -------------------------------------------------------------------------------- /Xenon Reader/Helpers/LibraryHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LibraryHelpers.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/25/21. 6 | // 7 | 8 | import EPUBKit 9 | import Foundation 10 | import SwiftUI 11 | 12 | // MARK: Wrapper Struct 13 | 14 | struct LibraryLoader { 15 | @AppStorage("libraryPath") var libraryPath = "" 16 | @AppStorage("libraryUrl") var libraryUrl = "" 17 | let xrShared: XRShared 18 | 19 | func scanFiles() { 20 | self.xrShared.fileList = retrieveDirectoryList(libraryPath: self.libraryPath, showHidden: false, fileExtension: "epub") ?? [] 21 | self.xrShared.epubs = loadLibraryItems(libraryUrl: self.libraryUrl, directoryList: self.xrShared.fileList) 22 | self.xrShared.authors = loadAuthors(readableList: self.xrShared.epubs) 23 | self.xrShared.publishers = loadPublishers(readableList: self.xrShared.epubs) 24 | } 25 | } 26 | 27 | // MARK: Functions 28 | 29 | // TODO: Make function asynchronous 30 | func loadLibraryItems(libraryUrl: String, directoryList: [String]) -> [EpubLoader] { 31 | var epubs: [EpubLoader] = [] 32 | 33 | for filename in directoryList { 34 | let fileUrl = generateFileUrl(libraryUrl: libraryUrl, filename: filename.replacingOccurrences(of: ".epub", with: ""), fileExtension: "epub")! 35 | let epub = EpubLoader(withUrl: fileUrl) 36 | 37 | epubs.append(epub) 38 | } 39 | 40 | return epubs 41 | } 42 | -------------------------------------------------------------------------------- /Xenon Reader/Helpers/ReadableHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReadableHelpers.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/25/21. 6 | // 7 | 8 | import EPUBKit 9 | import Foundation 10 | 11 | // MARK: Structs 12 | 13 | struct Author: Identifiable, Hashable { 14 | let id = UUID() 15 | let name: String 16 | var readables: [EpubLoader] = [] 17 | } 18 | 19 | struct Publisher: Identifiable, Hashable { 20 | let id = UUID() 21 | let name: String 22 | var readables: [EpubLoader] = [] 23 | } 24 | 25 | // MARK: Loading Functions 26 | 27 | func loadAuthors(readableList: [EpubLoader]) -> [Author] { 28 | var authors: [Author] = [] 29 | 30 | for readable in readableList { 31 | if let readableAuthor = readable.epub?.author { 32 | var authorStruct: Author? 33 | 34 | for author in authors { 35 | if author.name == readableAuthor { 36 | authorStruct = author 37 | } 38 | } 39 | 40 | if authorStruct == nil { 41 | authors.append(Author(name: readableAuthor, readables: [readable])) 42 | } else { 43 | let index = authors.firstIndex(of: authorStruct!) ?? -1 44 | authorStruct?.readables.append(readable) 45 | authors[index] = authorStruct! 46 | } 47 | } 48 | } 49 | 50 | return authors 51 | } 52 | 53 | func loadPublishers(readableList: [EpubLoader]) -> [Publisher] { 54 | var publishers: [Publisher] = [] 55 | 56 | for readable in readableList { 57 | if let readablePublisher = readable.epub?.publisher { 58 | var publisherStruct: Publisher? 59 | 60 | for publisher in publishers { 61 | if publisher.name == readablePublisher { 62 | publisherStruct = publisher 63 | } 64 | } 65 | 66 | if publisherStruct == nil { 67 | publishers.append(Publisher(name: readablePublisher, readables: [readable])) 68 | } else { 69 | let index = publishers.firstIndex(of: publisherStruct!) ?? -1 70 | publisherStruct?.readables.append(readable) 71 | publishers[index] = publisherStruct! 72 | } 73 | } 74 | } 75 | 76 | return publishers 77 | } 78 | -------------------------------------------------------------------------------- /Xenon Reader/Helpers/Realm/RealmHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealmHelpers.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/19/21. 6 | // 7 | 8 | import Foundation 9 | //import RealmSwift 10 | // 11 | //func initializeRealm() -> Realm? { 12 | // do { 13 | // return try Realm() 14 | // } catch { 15 | // print("Error initalizing new Realm: \(error.localizedDescription)") 16 | // return nil 17 | // } 18 | //} 19 | -------------------------------------------------------------------------------- /Xenon Reader/Helpers/Realm/RealmObjects.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealmObjects.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/17/21. 6 | // 7 | 8 | import Foundation 9 | //import RealmSwift 10 | // 11 | //class Category: Object { 12 | // @objc dynamic var id: String = UUID().uuidString 13 | // @objc dynamic var name: String = "" 14 | // @objc dynamic var imageName: String = "tray.circle" 15 | // @objc dynamic var creationDate: Date = Date() 16 | // var readables: [String] = [] 17 | // 18 | // override static func primaryKey() -> String? { 19 | // "id" 20 | // } 21 | //} 22 | 23 | struct ReadableCategory { 24 | var id: String 25 | var name: String 26 | var imageName: String 27 | var creationDate: Date 28 | var readables: [String] = [] 29 | } 30 | -------------------------------------------------------------------------------- /Xenon Reader/Helpers/View Helpers/LibraryBookModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LibraryBookModifier.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/25/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | struct LibraryBookModifier: ViewModifier { 12 | @State private var informationSheet: Bool = false 13 | let epub: EPUBDocument? 14 | 15 | func body(content: Content) -> some View { 16 | content 17 | .contextMenu { 18 | Button(action: {}) { 19 | Label("Read \"\(returnPrefix(string: epub?.title ?? "Unknown Title", prefixLength: 16))\"", systemImage: "book") 20 | } 21 | 22 | Button(action: { 23 | informationSheet = true 24 | }) { 25 | Label("Information", systemImage: "info.circle") 26 | } 27 | 28 | Divider() 29 | 30 | Button(action: {}) { 31 | Label("Add to Category", systemImage: "tray") 32 | } 33 | } 34 | .sheet(isPresented: $informationSheet) { 35 | ReadableInformation(isPresented: $informationSheet, epub: epub) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Xenon Reader/Helpers/View Helpers/TOCFocusModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TOCFocusModifier.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 3/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TOCFocusModifier: ViewModifier { 11 | let buttonText: String 12 | let activeChapter: String? 13 | 14 | func body(content: Content) -> some View { 15 | Group { 16 | if activeChapter != nil && activeChapter == buttonText { 17 | content 18 | .background(Color.accentColor) 19 | } else { 20 | content 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Xenon Reader/Helpers/View Helpers/ToolbarModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolbarModifier.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/16/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ToolbarModifier: ViewModifier { 11 | @AppStorage("libraryViewType") var viewType: LibraryViewTypes = .grid 12 | @State var xrShared: XRShared 13 | 14 | func body(content: Content) -> some View { 15 | content 16 | .toolbar { 17 | ToolbarItemGroup(placement: .navigation) { 18 | if xrShared.mainViewType == .reader { 19 | Button(action: { 20 | self.xrShared.activeReadable = nil 21 | self.xrShared.mainViewType = .library 22 | }) { 23 | Image(systemName: "chevron.left") 24 | } 25 | } 26 | 27 | Button(action: toggleSidebar) { 28 | Image(systemName: "sidebar.left") 29 | .help("Toggle Sidebar") 30 | } 31 | } 32 | 33 | ToolbarItemGroup { 34 | if xrShared.mainViewType == .library { 35 | // MARK: Library Controls 36 | 37 | Picker("Library View", selection: $viewType) { 38 | Label("Grid", systemImage: "square.grid.3x2") 39 | .tag(LibraryViewTypes.grid) 40 | Label("List", systemImage: "tablecells") 41 | .tag(LibraryViewTypes.list) 42 | } 43 | .pickerStyle(SegmentedPickerStyle()) 44 | 45 | // TODO: Add keyboard shortcut (slash) for searching 46 | Button(action: { 47 | print("Searching...") 48 | }) { 49 | Image(systemName: "magnifyingglass") 50 | } 51 | } else { 52 | // MARK: Reader Controls 53 | 54 | Button(action: { 55 | if xrShared.activeReadable != nil, xrShared.activeReadable!.spineItemIndex - 1 >= 0 { 56 | xrShared.activeReadable?.spineItemIndex -= 1 57 | } 58 | }) { 59 | Label("Previous Page", systemImage: "chevron.left") 60 | } 61 | Button(action: { 62 | if xrShared.activeReadable != nil, xrShared.activeReadable!.spineItemIndex + 1 < (xrShared.activeReadable?.epub?.spine.items.count) ?? 0 { 63 | xrShared.activeReadable?.spineItemIndex += 1 64 | } 65 | }) { 66 | Label("Next Page", systemImage: "chevron.right") 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Xenon Reader/Helpers/View Helpers/ViewHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewHelpers.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/20/21. 6 | // 7 | 8 | import Cocoa 9 | import EPUBKit 10 | import Foundation 11 | 12 | // MARK: Enums 13 | 14 | enum LibraryViewTypes: String, CaseIterable, Identifiable { 15 | case grid 16 | case list 17 | 18 | var id: String { self.rawValue } 19 | } 20 | 21 | enum MainViewType: String, CaseIterable, Identifiable { 22 | case library 23 | case reader 24 | 25 | var id: String { self.rawValue } 26 | } 27 | 28 | enum LibrarySortTypes: String, CaseIterable, Identifiable { 29 | case title 30 | case titleReversed 31 | case author 32 | case authorReversed 33 | case dateAdded 34 | case lastViewed 35 | case publisher 36 | case publisherReversed 37 | 38 | var id: String { self.rawValue } 39 | } 40 | 41 | enum ReaderSidebarViews: String, CaseIterable, Identifiable { 42 | case tableOfContents 43 | case metadata 44 | case markup 45 | 46 | var id: String { self.rawValue } 47 | } 48 | 49 | // MARK: Functions 50 | 51 | // MARK: Generation Functions 52 | 53 | func generateReadableCount(count: Int) -> String { 54 | return count == 1 ? "\(count) Readable" : "\(count) Readables" 55 | } 56 | 57 | func generateSubtitle(xrShared: XRShared) -> String { 58 | var subtitle: String = "" 59 | 60 | switch xrShared.mainViewType { 61 | case .library: subtitle = generateReadableCount(count: xrShared.epubs.count) 62 | case .reader: 63 | if let activeReadable = xrShared.activeReadable, let activeChapter = getActiveChapterTitle(activeReadable: activeReadable) { 64 | subtitle = activeChapter 65 | } 66 | } 67 | 68 | return subtitle 69 | } 70 | 71 | // MARK: Generic Functions 72 | 73 | func toggleSidebar() { 74 | NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil) 75 | } 76 | 77 | func returnPrefix(string: String, prefixLength: Int) -> String { 78 | let prefixedString = String(string.prefix(prefixLength)) 79 | return (prefixedString.count < string.count) ? prefixedString.appending("...") : prefixedString 80 | } 81 | 82 | func sortLibraryList(epubList: [EpubLoader], sortType: LibrarySortTypes) -> [EpubLoader] { 83 | var returnList: [EpubLoader] 84 | 85 | switch sortType { 86 | case .title: returnList = epubList.sorted { $0.epub?.title?.lowercased() ?? "no title" < $1.epub?.title?.lowercased() ?? "no title" } 87 | case .titleReversed: returnList = epubList.sorted { $1.epub?.title?.lowercased() ?? "no title" < $0.epub?.title?.lowercased() ?? "no title" } 88 | case .author: returnList = epubList.sorted { $0.epub?.author?.lowercased() ?? "no title" < $1.epub?.author?.lowercased() ?? "no title" } 89 | case .authorReversed: returnList = epubList.sorted { $1.epub?.author?.lowercased() ?? "no title" < $0.epub?.author?.lowercased() ?? "no title" } 90 | case .publisher: returnList = epubList.sorted { $0.epub?.publisher?.lowercased() ?? "no title" < $1.epub?.publisher?.lowercased() ?? "no title" } 91 | case .publisherReversed: returnList = epubList.sorted { $1.epub?.publisher?.lowercased() ?? "no title" < $0.epub?.publisher?.lowercased() ?? "no title" } 92 | default: returnList = epubList 93 | } 94 | 95 | return returnList 96 | } 97 | 98 | // MARK: Generation Helpers 99 | 100 | func getActiveChapterTitle(activeReadable: EpubLoader) -> String? { 101 | let spineItems = activeReadable.epub?.spine.items 102 | let manifestItems = activeReadable.epub?.manifest.items.map { $0.1 } 103 | let tocItems = activeReadable.epub?.tableOfContents.subTable 104 | 105 | if let tocItem = tocItems?.first(where: { $0.item == manifestItems?.first(where: { $0.id == spineItems?[activeReadable.spineItemIndex].idref })?.path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) }) { 106 | return tocItem.label 107 | } else { 108 | return nil 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Xenon Reader/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeRole 11 | None 12 | LSHandlerRank 13 | Default 14 | 15 | 16 | CFBundleExecutable 17 | $(EXECUTABLE_NAME) 18 | CFBundleIdentifier 19 | $(PRODUCT_BUNDLE_IDENTIFIER) 20 | CFBundleInfoDictionaryVersion 21 | 6.0 22 | CFBundleName 23 | $(PRODUCT_NAME) 24 | CFBundlePackageType 25 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 26 | CFBundleShortVersionString 27 | $(MARKETING_VERSION) 28 | CFBundleVersion 29 | 1 30 | LSApplicationCategoryType 31 | public.app-category.books 32 | LSMinimumSystemVersion 33 | $(MACOSX_DEPLOYMENT_TARGET) 34 | NSHumanReadableCopyright 35 | Created by H. Kamran 36 | 37 | 38 | -------------------------------------------------------------------------------- /Xenon Reader/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Library/AuthorsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthorsView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/16/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AuthorsView: View { 11 | @EnvironmentObject var xrShared: XRShared 12 | 13 | var body: some View { 14 | NavigationView { 15 | List(self.xrShared.authors.sorted { $0.name.lowercased() < $1.name.lowercased() }, id: \.id) { author in 16 | NavigationLink(destination: LibraryView(epubs: author.readables)) { 17 | Text(author.name) 18 | } 19 | } 20 | } 21 | } 22 | } 23 | 24 | #if DEBUG 25 | struct AuthorsView_Previews: PreviewProvider { 26 | static var previews: some View { 27 | AuthorsView() 28 | } 29 | } 30 | #endif 31 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Library/Library View Types/GridView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GridView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/16/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | struct GridView: View { 12 | @AppStorage("librarySortType") var librarySort: LibrarySortTypes = .title 13 | @EnvironmentObject var xrShared: XRShared 14 | 15 | let epubs: [EpubLoader] 16 | 17 | let columns = [ 18 | GridItem(.adaptive(minimum: 150)) 19 | ] 20 | 21 | var body: some View { 22 | ScrollView { 23 | LazyVGrid(columns: columns, alignment: .leading, spacing: 20) { 24 | ForEach(sortLibraryList(epubList: epubs, sortType: librarySort), id: \.id) { epub in 25 | GridViewBook(epub: epub.epub) 26 | .onTapGesture(count: 2, perform: { 27 | self.xrShared.activeReadable = epub 28 | self.xrShared.mainViewType = .reader 29 | }) 30 | } 31 | } 32 | .padding(.horizontal) 33 | } 34 | } 35 | } 36 | 37 | #if DEBUG 38 | struct GridView_Previews: PreviewProvider { 39 | static var previews: some View { 40 | GridView(epubs: []) 41 | .environmentObject(XRShared()) 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Library/Library View Types/GridViewBook.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GridViewBook.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/16/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | struct GridViewBook: View { 12 | let epub: EPUBDocument? 13 | @State private var informationSheet: Bool = false 14 | 15 | var body: some View { 16 | VStack(alignment: .center) { 17 | Image(nsImage: loadImage(epub?.cover) ?? NSImage()) 18 | .resizable() 19 | .aspectRatio(contentMode: .fit) 20 | .frame(width: 150, height: 250) 21 | 22 | Group { 23 | Text(epub?.title ?? "Unknown Title") 24 | .font(.headline) 25 | .bold() 26 | .multilineTextAlignment(.center) 27 | 28 | Text(epub?.author ?? "Unknown Author") 29 | .font(.subheadline) 30 | .multilineTextAlignment(.center) 31 | } 32 | .frame(width: 150) 33 | } 34 | .modifier(LibraryBookModifier(epub: epub)) 35 | } 36 | } 37 | 38 | #if DEBUG 39 | struct GridViewBook_Previews: PreviewProvider { 40 | static var previews: some View { 41 | GridViewBook(epub: EPUBDocument(url: URL(string: "file:///Users/hkamran/Desktop/Desktop/Books/Xenon%20Library/You%20Are%20Enough.epub")!)) 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Library/Library View Types/ListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/22/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | struct ListView: View { 12 | @AppStorage("librarySortType") var librarySort: LibrarySortTypes = .title 13 | @EnvironmentObject var xrShared: XRShared 14 | 15 | let epubs: [EpubLoader] 16 | var body: some View { 17 | List { 18 | // TODO: Figure out why the spacing gets all out of whack when the sort type changes, then fixes itself if you switch the view type to grid then switch it back 19 | ForEach(sortLibraryList(epubList: epubs, sortType: librarySort), id: \.id) { epub in 20 | ListViewBook(epub: epub.epub) 21 | .onTapGesture(count: 2, perform: { 22 | self.xrShared.activeReadable = epub 23 | self.xrShared.mainViewType = .reader 24 | }) 25 | } 26 | } 27 | } 28 | } 29 | 30 | #if DEBUG 31 | struct ListView_Previews: PreviewProvider { 32 | static var previews: some View { 33 | ListView(epubs: []) 34 | .environmentObject(XRShared()) 35 | } 36 | } 37 | #endif 38 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Library/Library View Types/ListViewBook.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListViewBook.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/22/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | struct ListViewBook: View { 12 | let epub: EPUBDocument? 13 | 14 | var body: some View { 15 | VStack(alignment: .leading) { 16 | Text(epub?.title ?? "Unknown Title") 17 | .font(.headline) 18 | .bold() 19 | 20 | Text(epub?.author ?? "Unknown Author") 21 | .font(.subheadline) 22 | } 23 | .modifier(LibraryBookModifier(epub: epub)) 24 | } 25 | } 26 | 27 | #if DEBUG 28 | struct ListViewBook_Previews: PreviewProvider { 29 | static var previews: some View { 30 | ListViewBook(epub: EPUBDocument(url: URL(string: "file:///Users/hkamran/Desktop/Desktop/Books/Xenon%20Library/You%20Are%20Enough.epub")!)) 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Library/LibraryParentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LibraryParentView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/24/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct LibraryParentView: View { 11 | @EnvironmentObject var xrShared: XRShared 12 | 13 | var body: some View { 14 | SidebarView() 15 | .environmentObject(self.xrShared) 16 | 17 | LibraryView(epubs: xrShared.epubs) 18 | .environmentObject(self.xrShared) 19 | } 20 | } 21 | 22 | #if DEBUG 23 | struct LibraryParentView_Previews: PreviewProvider { 24 | static var previews: some View { 25 | LibraryParentView() 26 | .environmentObject(XRShared()) 27 | } 28 | } 29 | #endif 30 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Library/LibraryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LibraryView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/22/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | struct LibraryView: View { 12 | @AppStorage("libraryViewType") var viewType: LibraryViewTypes = .grid 13 | @EnvironmentObject var xrShared: XRShared 14 | 15 | let epubs: [EpubLoader] 16 | var body: some View { 17 | switch viewType { 18 | case .grid: GridView(epubs: epubs).environmentObject(self.xrShared) 19 | case .list: ListView(epubs: epubs).environmentObject(self.xrShared) 20 | } 21 | } 22 | } 23 | 24 | #if DEBUG 25 | struct LibraryView_Previews: PreviewProvider { 26 | static var previews: some View { 27 | LibraryView(epubs: []) 28 | .environmentObject(XRShared()) 29 | } 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Library/PublishersView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PublishersView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/22/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PublishersView: View { 11 | @EnvironmentObject var xrShared: XRShared 12 | 13 | var body: some View { 14 | NavigationView { 15 | List(self.xrShared.publishers.sorted { $0.name.lowercased() < $1.name.lowercased() }, id: \.id) { publisher in 16 | NavigationLink(destination: LibraryView(epubs: publisher.readables)) { 17 | Text(publisher.name) 18 | } 19 | } 20 | } 21 | } 22 | } 23 | 24 | struct PublishersView_Previews: PreviewProvider { 25 | static var previews: some View { 26 | PublishersView() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Library/Sub-views/ReadableInformation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReadableInformation.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/21/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | struct ReadableInformation: View { 12 | @Binding var isPresented: Bool 13 | 14 | let epub: EPUBDocument? 15 | 16 | var body: some View { 17 | HStack(spacing: 20) { 18 | Image(nsImage: loadImage(epub?.cover) ?? NSImage()) 19 | .resizable() 20 | .aspectRatio(contentMode: .fit) 21 | .frame(width: 150, height: 250) 22 | 23 | VStack(alignment: .leading, spacing: 5) { 24 | HStack(alignment: .center) { 25 | VStack(alignment: .leading) { 26 | Text(epub?.title ?? "Unknown Title") 27 | .font(.headline) 28 | .bold() 29 | 30 | Text(epub?.author ?? "Unknown Author") 31 | .font(.subheadline) 32 | } 33 | 34 | Spacer() 35 | 36 | Button(action: { 37 | withAnimation { 38 | isPresented = false 39 | } 40 | }) { 41 | Image(systemName: "xmark.circle") 42 | .resizable() 43 | .aspectRatio(contentMode: .fit) 44 | .frame(width: 20, height: 20) 45 | } 46 | .buttonStyle(PlainButtonStyle()) 47 | .keyboardShortcut(.cancelAction) 48 | } 49 | 50 | Divider() 51 | 52 | List { 53 | if let publisher = epub?.publisher { 54 | DetailListRow(name: "Publisher", detail: publisher) 55 | } 56 | 57 | if let description = epub?.metadata.description { 58 | DetailListRow(name: "Description", detail: (description.stripOutHtml() ?? description).trimmingCharacters(in: .whitespacesAndNewlines)) 59 | } 60 | } 61 | } 62 | } 63 | .padding(.all) 64 | .frame(minWidth: 750, maxWidth: 850, minHeight: 325, maxHeight: 400) 65 | } 66 | } 67 | 68 | #if DEBUG 69 | struct ReadableInformation_Previews: PreviewProvider { 70 | static var previews: some View { 71 | ReadableInformation(isPresented: .constant(true), epub: EPUBDocument(url: URL(string: "file:///Users/hkamran/Desktop/Desktop/Books/Xenon%20Library/SpySchoolBritishInvasion_StuartGibbs.epub")!)) 72 | } 73 | } 74 | #endif 75 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Library/Sub-views/SidebarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SidebarView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/20/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SidebarView: View { 11 | @EnvironmentObject var xrShared: XRShared 12 | @State private var defaultItemActive: Bool = true 13 | 14 | var body: some View { 15 | List { 16 | Section(header: Text("Library")) { 17 | NavigationLink(destination: LibraryView(epubs: xrShared.epubs), isActive: $defaultItemActive) { 18 | Label("All Books", systemImage: "books.vertical") 19 | } 20 | NavigationLink(destination: AuthorsView()) { 21 | Label("Authors", systemImage: "person.3") 22 | } 23 | NavigationLink(destination: PublishersView()) { 24 | Label("Publishers", systemImage: "rectangle.stack.person.crop") 25 | } 26 | } 27 | 28 | // TODO: Add category support 29 | // Section(header: Text("Categories")) { 30 | // ForEach(self.xrShared.categories, id: \.id) { category in 31 | // NavigationLink(destination: Text("Planned: \(category.name)")) { 32 | // Label(category.name, systemImage: category.imageName) 33 | // } 34 | // } 35 | // } 36 | 37 | Spacer() 38 | 39 | Section(header: Text("Testing")) { 40 | NavigationLink( 41 | destination: FileListView() 42 | .environmentObject(self.xrShared)) { 43 | Label("File List View", systemImage: "doc") 44 | } 45 | NavigationLink(destination: VariablesView()) { 46 | Label("Variables", systemImage: "externaldrive.connected.to.line.below") 47 | } 48 | } 49 | } 50 | .listStyle(SidebarListStyle()) 51 | } 52 | } 53 | 54 | #if DEBUG 55 | struct SidebarView_Previews: PreviewProvider { 56 | static var previews: some View { 57 | SidebarView() 58 | } 59 | } 60 | #endif 61 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Reader/ReaderParentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReaderParentView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/24/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | struct ReaderParentView: View { 12 | @EnvironmentObject var xrShared: XRShared 13 | 14 | var body: some View { 15 | Group { 16 | if xrShared.activeReadable != nil { 17 | ReaderSidebarView() 18 | .environmentObject(self.xrShared) 19 | 20 | ReaderRenderView(activeReadable: xrShared.activeReadable, filename: xrShared.activeReadable?.epub?.manifest.items[(xrShared.activeReadable?.epub?.spine.items[xrShared.activeReadable?.spineItemIndex ?? 0].idref)!]?.path) 21 | 22 | } else { 23 | EmptyView() 24 | Text("No Active Readable") 25 | } 26 | } 27 | } 28 | } 29 | 30 | #if DEBUG 31 | struct ReaderParentView_Previews: PreviewProvider { 32 | static var previews: some View { 33 | ReaderParentView() 34 | .environmentObject(XRShared()) 35 | } 36 | } 37 | #endif 38 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Reader/ReaderRenderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReaderRenderView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/24/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | struct ReaderRenderView: View { 12 | let activeReadable: EpubLoader? 13 | let filename: String? 14 | 15 | var body: some View { 16 | Group { 17 | if 18 | let fileUrl = getEpubPageUrl(epubFilename: activeReadable!.id, path: filename ?? ""), 19 | let directoryUrl = getEpubPageDirectoryUrl(epubId: activeReadable!.id, storageLocation: .applicationSupport) 20 | { 21 | FileWebView(fileURL: fileUrl, directoryURL: directoryUrl) 22 | } else { 23 | ProgressView() 24 | .progressViewStyle(CircularProgressViewStyle()) 25 | } 26 | } 27 | } 28 | } 29 | 30 | #if DEBUG 31 | struct ReaderRenderView_Previews: PreviewProvider { 32 | static var previews: some View { 33 | ReaderRenderView(activeReadable: EpubLoader(withUrl: URL(string: "file:///Users/hkamran/Desktop/Desktop/Books/Xenon%20Library/You%20Are%20Enough.epub")!), filename: "") 34 | } 35 | } 36 | #endif 37 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Reader/ReaderSidebarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReaderSidebarView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/24/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | struct ReaderSidebarView: View { 12 | @EnvironmentObject var xrShared: XRShared 13 | @State private var sidebarView: ReaderSidebarViews = .tableOfContents 14 | 15 | var body: some View { 16 | VStack(alignment: .leading) { 17 | Picker("", selection: $sidebarView) { 18 | Label("Table of Contents", systemImage: "list.bullet") 19 | .labelStyle(IconOnlyLabelStyle()) 20 | .tag(ReaderSidebarViews.tableOfContents) 21 | 22 | Label("Metadata", systemImage: "info.circle") 23 | .labelStyle(IconOnlyLabelStyle()) 24 | .tag(ReaderSidebarViews.metadata) 25 | 26 | Label("Markup", systemImage: "pencil.and.outline") 27 | .labelStyle(IconOnlyLabelStyle()) 28 | .tag(ReaderSidebarViews.markup) 29 | } 30 | .pickerStyle(SegmentedPickerStyle()) 31 | .padding(.horizontal, 8) 32 | 33 | // TODO: Figure out why switching sidebar views changes the web view 34 | switch sidebarView { 35 | case .tableOfContents: ReaderSidebarTOC().environmentObject(xrShared) 36 | case .metadata: ReaderSidebarMetadata().environmentObject(xrShared) 37 | default: ReaderSidebarTOC().environmentObject(xrShared) 38 | } 39 | } 40 | } 41 | } 42 | 43 | #if DEBUG 44 | struct ReaderSidebarView_Previews: PreviewProvider { 45 | static var previews: some View { 46 | ReaderSidebarView() 47 | .environmentObject(XRShared()) 48 | } 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Reader/Sub-views/ReaderSidebarMetadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReaderSidebarMetadata.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 3/11/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ReaderSidebarMetadata: View { 11 | @EnvironmentObject var xrShared: XRShared 12 | 13 | var body: some View { 14 | List { 15 | Section(header: Text("Details")) { 16 | DetailListRow(name: "Title", detail: xrShared.activeReadable?.epub?.title ?? "Unknown Title") 17 | DetailListRow(name: "Author", detail: xrShared.activeReadable?.epub?.author ?? "Unknown Author") 18 | DetailListRow(name: "Publisher", detail: xrShared.activeReadable?.epub?.publisher ?? "Unknown Publisher") 19 | } 20 | 21 | Section(header: Text("Counts")) { 22 | DetailListRow(name: "Chapter Count", detail: String(xrShared.activeReadable?.epub?.tableOfContents.subTable?.count ?? -1)) 23 | DetailListRow(name: "Page Count", detail: String(xrShared.activeReadable?.epub?.spine.items.count ?? -1)) 24 | } 25 | } 26 | } 27 | } 28 | 29 | #if DEBUG 30 | struct ReaderSidebarMetadata_Previews: PreviewProvider { 31 | static var previews: some View { 32 | ReaderSidebarMetadata() 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Reader/Sub-views/ReaderSidebarTOC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReaderSidebarTOC.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 3/11/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ReaderSidebarTOC: View { 11 | @EnvironmentObject var xrShared: XRShared 12 | 13 | var body: some View { 14 | List { 15 | if let activeReadable = xrShared.activeReadable, 16 | let subTable = activeReadable.epub?.tableOfContents.subTable 17 | { 18 | ForEach(subTable, id: \.id) { item in 19 | Button(action: { 20 | let spineItems = activeReadable.epub?.spine.items 21 | let manifestItems = activeReadable.epub?.manifest.items.map { $0.1 } 22 | let itemNoPercent = item.item?.removingPercentEncoding 23 | 24 | if let spineIndex = spineItems?.firstIndex(where: { $0.idref == manifestItems?.first(where: { $0.path == itemNoPercent })?.id }) { 25 | xrShared.activeReadable?.spineItemIndex = spineIndex 26 | } 27 | }) { 28 | Text(item.label) 29 | } 30 | .buttonStyle(PlainButtonStyle()) 31 | .frame(width: 180, height: 15, alignment: .leading) 32 | .padding(.leading, 4) 33 | .padding(.vertical, 2) 34 | .modifier(TOCFocusModifier(buttonText: item.label, activeChapter: getActiveChapterTitle(activeReadable: activeReadable))) 35 | } 36 | } else { 37 | Text("No TOC Available") 38 | } 39 | } 40 | .listStyle(SidebarListStyle()) 41 | } 42 | } 43 | 44 | #if DEBUG 45 | struct ReaderSidebarTOC_Previews: PreviewProvider { 46 | static var previews: some View { 47 | ReaderSidebarTOC() 48 | } 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Settings/DebugSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DebugSettingsView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/16/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct DebugSettingsView: View { 11 | @AppStorage("fontSize") private var fontSize = 12.0 12 | @AppStorage("libraryPath") var libraryPath = "" 13 | @AppStorage("libraryUrl") var libraryUrl = "" 14 | 15 | var body: some View { 16 | Form { 17 | HStack { 18 | Text(libraryPath) 19 | 20 | Button("Select Folder") { 21 | let panel = NSOpenPanel() 22 | panel.allowsMultipleSelection = false 23 | panel.canChooseDirectories = true 24 | panel.canChooseFiles = false 25 | 26 | if panel.runModal() == .OK { 27 | let libraryPath = panel.url?.absoluteString ?? FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].absoluteString 28 | 29 | self.libraryPath = libraryPath.replacingOccurrences(of: "file://", with: "").removingPercentEncoding ?? "" 30 | self.libraryUrl = libraryPath 31 | } 32 | } 33 | 34 | Button(action: { 35 | libraryPath = "" 36 | }) { 37 | Image(systemName: "arrow.clockwise.circle") 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | #if DEBUG 45 | struct DebugSettingsView_Previews: PreviewProvider { 46 | static var previews: some View { 47 | DebugSettingsView() 48 | } 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Settings/LibrarySettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LibrarySettingsView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 3/9/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct LibrarySettingsView: View { 11 | @AppStorage("libraryPath") var libraryPath = "" 12 | @AppStorage("libraryUrl") var libraryUrl = "" 13 | @AppStorage("libraryViewType") var viewType: LibraryViewTypes = .grid 14 | @AppStorage("librarySortType") var librarySort: LibrarySortTypes = .title 15 | 16 | var body: some View { 17 | Form { 18 | Section { 19 | HStack { 20 | Text("Library Folder") 21 | .bold() 22 | 23 | Text(libraryPath) 24 | 25 | Button("Select Folder") { 26 | let panel = NSOpenPanel() 27 | 28 | panel.allowsMultipleSelection = false 29 | panel.canChooseDirectories = true 30 | panel.canChooseFiles = false 31 | 32 | panel.title = "Xenon Reader Library Path" 33 | panel.message = "Where your EPUBs are stored" 34 | panel.prompt = "Select" 35 | 36 | if panel.runModal() == .OK { 37 | let libraryPath = panel.url?.absoluteString ?? FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].absoluteString 38 | 39 | self.libraryPath = libraryPath.replacingOccurrences(of: "file://", with: "").removingPercentEncoding ?? "" 40 | self.libraryUrl = libraryPath 41 | } 42 | } 43 | } 44 | } 45 | 46 | Section { 47 | Picker("Library View", selection: $viewType) { 48 | Label("Grid", systemImage: "square.grid.3x2") 49 | .tag(LibraryViewTypes.grid) 50 | Label("List", systemImage: "tablecells") 51 | .tag(LibraryViewTypes.list) 52 | } 53 | .pickerStyle(SegmentedPickerStyle()) 54 | } 55 | 56 | Section { 57 | Picker("Library Sort", selection: $librarySort) { 58 | Label("Title", systemImage: "textformat") 59 | .tag(LibrarySortTypes.title) 60 | Label("Title (Reversed)", systemImage: "textformat") 61 | .tag(LibrarySortTypes.titleReversed) 62 | 63 | Label("Author", systemImage: "person.3") 64 | .tag(LibrarySortTypes.author) 65 | Label("Author (Reversed)", systemImage: "person.3") 66 | .tag(LibrarySortTypes.authorReversed) 67 | 68 | Label("Publisher", systemImage: "rectangle.stack.person.crop") 69 | .tag(LibrarySortTypes.publisher) 70 | Label("Publisher (Reversed)", systemImage: "rectangle.stack.person.crop") 71 | .tag(LibrarySortTypes.publisherReversed) 72 | 73 | Label("Date Added", systemImage: "calendar.badge.plus") 74 | .tag(LibrarySortTypes.dateAdded) 75 | Label("Last Viewed", systemImage: "eyeglasses") 76 | .tag(LibrarySortTypes.lastViewed) 77 | } 78 | } 79 | } 80 | .padding(20) 81 | } 82 | } 83 | 84 | #if DEBUG 85 | struct LibrarySettingsView_Previews: PreviewProvider { 86 | static var previews: some View { 87 | LibrarySettingsView() 88 | } 89 | } 90 | #endif 91 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Settings/ReaderSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReaderSettingsView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 3/9/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ReaderSettingsView: View { 11 | @AppStorage("fontSize") var fontSize = 12.0 12 | 13 | var body: some View { 14 | Slider(value: $fontSize, in: 8 ... 64) { 15 | Text("Font Size (\(fontSize, specifier: "%.0f") pts)") 16 | } 17 | } 18 | } 19 | 20 | #if DEBUG 21 | struct ReaderSettingsView_Previews: PreviewProvider { 22 | static var previews: some View { 23 | ReaderSettingsView() 24 | } 25 | } 26 | #endif 27 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Settings/SettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/16/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SettingsView: View { 11 | private enum Tabs: Hashable { 12 | case library, reader 13 | } 14 | 15 | var body: some View { 16 | TabView { 17 | LibrarySettingsView() 18 | .tabItem { 19 | Label("Library", systemImage: "books.vertical") 20 | } 21 | .tag(Tabs.library) 22 | 23 | ReaderSettingsView() 24 | .tabItem { 25 | Label("Reader", systemImage: "book") 26 | } 27 | .tag(Tabs.reader) 28 | } 29 | .padding(20) 30 | .frame(minWidth: 350, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity) 31 | } 32 | } 33 | 34 | #if DEBUG 35 | struct SettingsView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | SettingsView() 38 | } 39 | } 40 | #endif 41 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Sub-views/DetailListRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListRow.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 3/11/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct DetailListRow: View { 11 | let name: String 12 | let detail: String 13 | 14 | var body: some View { 15 | VStack(alignment: .leading) { 16 | Text(name) 17 | .font(.subheadline) 18 | 19 | Text(detail) 20 | .font(.headline) 21 | .bold() 22 | } 23 | } 24 | } 25 | 26 | #if DEBUG 27 | struct DetailListRow_Previews: PreviewProvider { 28 | static var previews: some View { 29 | DetailListRow(name: "Example", detail: "Example Detail") 30 | } 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Sub-views/FileWebView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileWebView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/26/21. 6 | // 7 | 8 | import Cocoa 9 | import SwiftUI 10 | import WebKit 11 | 12 | struct FileWebView: NSViewRepresentable { 13 | let fileURL: URL 14 | let directoryURL: URL 15 | 16 | func makeNSView(context: Context) -> WKWebView { 17 | // TODO: Handle link elements in light mode (maybe a nice blue?) 18 | let css = ":root { color-scheme: light dark; } body { padding: 16px; font: -apple-system-body !important; font-size: 24px; } a { color: yellow }" 19 | let styleInjectionScript = "let style = document.createElement('style'); style.innerHTML = '\(css)'; document.head.appendChild(style);" 20 | let userScript = WKUserScript(source: styleInjectionScript, injectionTime: .atDocumentEnd, forMainFrameOnly: false) 21 | 22 | let userContentController = WKUserContentController() 23 | userContentController.addUserScript(userScript) 24 | 25 | let configuration = WKWebViewConfiguration() 26 | configuration.userContentController = userContentController 27 | 28 | return WKWebView(frame: .zero, configuration: configuration) 29 | } 30 | 31 | func updateNSView(_ nsView: WKWebView, context: Context) { 32 | nsView.loadFileURL(fileURL, allowingReadAccessTo: directoryURL) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Sub-views/NewCategorySheet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewCategorySheet.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/22/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | let sfSymbolsMini: [String] = [ 11 | "book", 12 | "book.circle", 13 | "books.vertical", 14 | "book.closed", 15 | "text.book.closed", 16 | "bookmark", 17 | "bookmark.circle", 18 | "folder", 19 | "folder.circle", 20 | "tray", 21 | "tray.circle" 22 | ] 23 | 24 | struct NewCategorySheet: View { 25 | @EnvironmentObject var xrShared: XRShared 26 | @Binding var isPresented: Bool 27 | 28 | @State var categoryName: String = "" 29 | @State var sfSymbol: String = "tray" 30 | 31 | var body: some View { 32 | Form { 33 | TextField("Category Name", text: $categoryName) 34 | .textFieldStyle(RoundedBorderTextFieldStyle()) 35 | .padding([.trailing, .bottom]) 36 | 37 | Picker("Icon", selection: $sfSymbol) { 38 | ForEach(sfSymbolsMini, id: \.self) { symbol in 39 | HStack { 40 | Image(systemName: symbol) 41 | Text(symbol) 42 | } 43 | } 44 | } 45 | .padding([.horizontal, .bottom]) 46 | 47 | Button(action: { 48 | self.xrShared.categories.append(ReadableCategory(id: UUID().uuidString, name: categoryName, imageName: sfSymbol, creationDate: Date())) 49 | }) { 50 | Text("Add Category") 51 | } 52 | .disabled(categoryName == "" && sfSymbol == "") 53 | } 54 | .padding(.vertical) 55 | } 56 | } 57 | 58 | #if DEBUG 59 | struct NewCategorySheet_Previews: PreviewProvider { 60 | static var previews: some View { 61 | NewCategorySheet(isPresented: .constant(false)) 62 | } 63 | } 64 | #endif 65 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Testing/FileListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileListView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/19/21. 6 | // 7 | 8 | import EPUBKit 9 | import SwiftUI 10 | 11 | struct FileListView: View { 12 | @AppStorage("libraryPath") var libraryPath = "" 13 | @AppStorage("libraryUrl") var libraryUrl = "" 14 | @EnvironmentObject var xrShared: XRShared 15 | 16 | @State var epub: EPUBDocument? 17 | 18 | var body: some View { 19 | VStack { 20 | Button(action: { 21 | self.xrShared.fileList = retrieveDirectoryList(libraryPath: libraryPath, showHidden: false, fileExtension: "epub") ?? [] 22 | }) { 23 | Text("Load Library Items") 24 | } 25 | 26 | List(self.xrShared.fileList, id: \.self) { file in 27 | Text(file) 28 | } 29 | } 30 | .padding() 31 | } 32 | } 33 | 34 | #if DEBUG 35 | struct FileListView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | FileListView() 38 | .environmentObject(XRShared()) 39 | } 40 | } 41 | #endif 42 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Testing/TReaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TReaderView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/22/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TReaderView: View { 11 | var body: some View { 12 | Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) 13 | } 14 | } 15 | 16 | struct TReaderView_Previews: PreviewProvider { 17 | static var previews: some View { 18 | TReaderView() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Xenon Reader/Views/Testing/VariablesView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VariablesView.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/20/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct VariablesView: View { 11 | @AppStorage("libraryPath") var libraryPath = "" 12 | @AppStorage("libraryUrl") var libraryUrl = "" 13 | @AppStorage("libraryViewType") var viewType: LibraryViewTypes = .grid 14 | @AppStorage("librarySortType") var librarySort: LibrarySortTypes = .title 15 | @AppStorage("fontSize") var fontSize = 12.0 16 | 17 | var body: some View { 18 | List { 19 | Section(header: Text("Library")) { 20 | Text("Library Path: \(libraryPath)") 21 | Text("Library URL: \(libraryUrl)") 22 | Text("Library View Type: \(viewType == .grid ? "Grid" : "List")") 23 | Text("Library Sort Type: \(librarySort.rawValue)") 24 | } 25 | 26 | Section(header: Text("Reader")) { 27 | Text("Font Size: \(fontSize) pts") 28 | } 29 | } 30 | } 31 | } 32 | 33 | #if DEBUG 34 | struct VariablesView_Previews: PreviewProvider { 35 | static var previews: some View { 36 | VariablesView() 37 | } 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /Xenon Reader/Xenon_Reader.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Xenon Reader/Xenon_ReaderApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Xenon_ReaderApp.swift 3 | // Xenon Reader 4 | // 5 | // Created by H. Kamran on 2/16/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct Xenon_ReaderApp: App { 12 | @AppStorage("libraryViewType") var viewType: LibraryViewTypes = .grid 13 | @AppStorage("librarySortType") var librarySort: LibrarySortTypes = .title 14 | @AppStorage("libraryPath") var libraryPath = "" 15 | @AppStorage("libraryUrl") var libraryUrl = "" 16 | @StateObject var xrShared = XRShared() 17 | 18 | @State private var categoryCreationSheet: Bool = false 19 | 20 | var body: some Scene { 21 | WindowGroup { 22 | ContentView() 23 | .onAppear(perform: { 24 | createAppDocumentsDirectory() 25 | LibraryLoader(libraryPath: libraryPath, libraryUrl: libraryUrl, xrShared: self.xrShared).scanFiles() 26 | }) 27 | .environmentObject(self.xrShared) 28 | } 29 | .commands { 30 | CommandGroup(before: .newItem) { 31 | // TODO: Add keyboard shortcut 32 | Button(action: { 33 | print("New category") 34 | }) { 35 | Text("New Category") 36 | } 37 | } 38 | 39 | CommandGroup(after: .newItem) { 40 | Divider() 41 | 42 | Button(action: LibraryLoader(libraryPath: libraryPath, libraryUrl: libraryUrl, xrShared: self.xrShared).scanFiles) { 43 | Text("Scan") 44 | } 45 | .keyboardShortcut("R", modifiers: .command) 46 | } 47 | 48 | CommandGroup(before: .sidebar) { 49 | Picker("Library View", selection: $viewType) { 50 | Label("Grid", systemImage: "square.grid.3x2") 51 | .tag(LibraryViewTypes.grid) 52 | Label("List", systemImage: "tablecells") 53 | .tag(LibraryViewTypes.list) 54 | } 55 | 56 | Picker("Library Sort", selection: $librarySort) { 57 | Label("Title", systemImage: "textformat") 58 | .tag(LibrarySortTypes.title) 59 | Label("Title (Reversed)", systemImage: "textformat") 60 | .tag(LibrarySortTypes.titleReversed) 61 | 62 | Label("Author", systemImage: "person.3") 63 | .tag(LibrarySortTypes.author) 64 | Label("Author (Reversed)", systemImage: "person.3") 65 | .tag(LibrarySortTypes.authorReversed) 66 | 67 | Label("Publisher", systemImage: "rectangle.stack.person.crop") 68 | .tag(LibrarySortTypes.publisher) 69 | Label("Publisher (Reversed)", systemImage: "rectangle.stack.person.crop") 70 | .tag(LibrarySortTypes.publisherReversed) 71 | 72 | Label("Date Added", systemImage: "calendar.badge.plus") 73 | .tag(LibrarySortTypes.dateAdded) 74 | Label("Last Viewed", systemImage: "eyeglasses") 75 | .tag(LibrarySortTypes.lastViewed) 76 | } 77 | } 78 | 79 | SidebarCommands() 80 | } 81 | 82 | #if os(macOS) 83 | Settings { 84 | SettingsView() 85 | } 86 | #endif 87 | } 88 | } 89 | --------------------------------------------------------------------------------