Skip to content
Permalink
Browse files

Extract model into shared BookPlayerKit framework

  • Loading branch information...
GianniCarlo committed Apr 23, 2019
1 parent 4f05fd6 commit 05f0f64c03303f2a7307d55e2cfcab6978620673
Showing with 1,140 additions and 740 deletions.
  1. +351 −98 BookPlayer.xcodeproj/project.pbxproj
  2. +1 −0 BookPlayer/AppDelegate.swift
  3. +5 −35 BookPlayer/Constants.swift
  4. +78 −0 BookPlayer/Extensions/Models/Book+AVFoundation.swift
  5. +5 −9 BookPlayer/{Models/Chapter+CoreDataClass.swift → Extensions/Models/Chapter+AVFoundation.swift}
  6. +19 −19 BookPlayer/Extensions/Notification+BookPlayer.swift
  7. +1 −0 BookPlayer/Extensions/UserDefaults+BookPlayer.swift
  8. +1 −0 BookPlayer/Library/AppNavigationController.swift
  9. +1 −0 BookPlayer/Library/Containers/MiniPlayerViewController.swift
  10. +17 −56 BookPlayer/Library/DataManagement/{DataManager.swift → DataManager+BookPlayer.swift}
  11. +6 −200 BookPlayer/Library/DataManagement/DataManager+CoreData.swift
  12. +1 −0 BookPlayer/Library/DataManagement/ImportManager.swift
  13. +4 −3 BookPlayer/Library/DataManagement/ImportOperation.swift
  14. +1 −0 BookPlayer/Library/ItemListViewController.swift
  15. +1 −0 BookPlayer/Library/ItemSelectionViewController.swift
  16. +1 −0 BookPlayer/Library/LibraryViewController.swift
  17. +1 −0 BookPlayer/Library/PlaylistViewController.swift
  18. +1 −0 BookPlayer/Library/Protocols/ItemList.swift
  19. +1 −0 BookPlayer/Library/Protocols/ItemListActions.swift
  20. +1 −0 BookPlayer/Library/Protocols/ItemListAlerts.swift
  21. +1 −0 BookPlayer/Library/RootViewController.swift
  22. +1 −0 BookPlayer/Library/Themes/ThemeManager.swift
  23. +1 −0 BookPlayer/Library/Views/AddButton.swift
  24. +1 −0 BookPlayer/Library/Views/AddCellView.swift
  25. +1 −0 BookPlayer/Library/Views/BookCellView.swift
  26. +1 −0 BookPlayer/Library/Views/CheckboxSelectionView.swift
  27. +1 −0 BookPlayer/Library/Views/ItemProgress.swift
  28. +1 −0 BookPlayer/Library/Views/LoadingView.swift
  29. +1 −0 BookPlayer/Library/Views/StaticCellView.swift
  30. +0 −208 BookPlayer/Models/Book+CoreDataClass.swift
  31. +1 −0 BookPlayer/Player/ChaptersViewController.swift
  32. +1 −0 BookPlayer/Player/Containers/PlayerControlsViewController.swift
  33. +1 −0 BookPlayer/Player/Containers/PlayerMetaViewController.swift
  34. +1 −0 BookPlayer/Player/PlayerManager.swift
  35. +1 −0 BookPlayer/Player/PlayerViewController.swift
  36. +1 −0 BookPlayer/Services/UserActivityManager.swift
  37. +1 −0 BookPlayer/Services/VoiceOverService.swift
  38. +1 −0 BookPlayer/Settings/Cells/IconCellView.swift
  39. +1 −0 BookPlayer/Settings/Cells/ThemeCellView.swift
  40. +14 −1 BookPlayer/Settings/IconsViewController.swift
  41. +1 −0 BookPlayer/Settings/PlayerSettingsViewController.swift
  42. +2 −1 BookPlayer/Settings/PlusBannerView.swift
  43. +1 −0 BookPlayer/Settings/PlusViewController.swift
  44. +1 −0 BookPlayer/Settings/SettingsViewController.swift
  45. +1 −0 BookPlayer/Settings/ThemesViewController.swift
  46. +19 −0 BookPlayerKit/BookPlayerKit.h
  47. +22 −0 BookPlayerKit/Info.plist
  48. +1 −0 BookPlayerTests/DataManagerTests.swift
  49. +1 −0 BookPlayerTests/ImportOperationTests.swift
  50. +1 −0 BookPlayerTests/PlaylistTests.swift
  51. +1 −0 BookPlayerTests/Services/BookSortServiceTest.swift
  52. +1 −0 BookPlayerTests/Services/VoiceOverServiceTest.swift
  53. +1 −0 BookPlayerTests/Support/StubFactory.swift
  54. +1 −1 {BookPlayer/Services → Shared}/BookSortService+SortError+PlayListSortOrder.swift
  55. +47 −0 Shared/Constants.swift
  56. +235 −0 Shared/DataManager.swift
  57. +1 −1 {BookPlayer/Library/DataManagement → Shared}/DeleteMode.swift
  58. +4 −4 {BookPlayer → Shared}/Extensions/UIColor+BookPlayer.swift
  59. +3 −3 {BookPlayer → Shared}/Extensions/UIColor+Sweetercolor.swift
  60. +144 −0 Shared/Models/Book+CoreDataClass.swift
  61. +3 −3 {BookPlayer → Shared}/Models/Book+CoreDataProperties.swift
  62. +3 −3 {BookPlayer → Shared}/Models/BookActivityItemProvider.swift
  63. +7 −7 {BookPlayer → Shared}/Models/BookPlayer.xcdatamodeld/Audiobook Player.xcdatamodel/contents
  64. +18 −0 Shared/Models/Chapter+CoreDataClass.swift
  65. +3 −3 {BookPlayer → Shared}/Models/Chapter+CoreDataProperties.swift
  66. +5 −5 {BookPlayer → Shared}/Models/FileItem.swift
  67. +10 −9 {BookPlayer → Shared}/Models/Library+CoreDataClass.swift
  68. +3 −3 {BookPlayer → Shared}/Models/Library+CoreDataProperties.swift
  69. +10 −9 {BookPlayer → Shared}/Models/LibraryItem+CoreDataClass.swift
  70. +3 −3 {BookPlayer → Shared}/Models/LibraryItem+CoreDataProperties.swift
  71. +3 −2 {BookPlayer → Shared}/Models/PlaybackRecord+CoreDataClass.swift
  72. +2 −2 {BookPlayer → Shared}/Models/PlaybackRecord+CoreDataProperties.swift
  73. +17 −16 {BookPlayer → Shared}/Models/Playlist+CoreDataClass.swift
  74. +3 −3 {BookPlayer → Shared}/Models/Playlist+CoreDataProperties.swift
  75. +30 −30 {BookPlayer → Shared}/Models/Theme+CoreDataClass.swift
  76. +3 −3 {BookPlayer → Shared}/Models/Theme+CoreDataProperties.swift

Large diffs are not rendered by default.

@@ -7,6 +7,7 @@
//
import AVFoundation
import BookPlayerKit
import DirectoryWatcher
import MediaPlayer
import Sentry
@@ -1,6 +1,10 @@
//
// Constants.swift
// Constants+BookPlayer.swift
// BookPlayer
//
// Created by Gianni Carlo on 4/23/19.
// Copyright © 2019 Tortuga Power. All rights reserved.
//
import Foundation

@@ -42,38 +46,4 @@ enum Constants {
}

static let UserActivityPlayback = Bundle.main.bundleIdentifier! + ".activity.playback"
static let ApplicationGroupIdentifier = "group." + Bundle.main.bundleIdentifier! + ".files"

enum DefaultArtworkColors {
case background
case primary
case secondary
case highlight

var lightColor: String {
switch self {
case .background:
return "#FAFAFA"
case .primary:
return "#37454E"
case .secondary:
return "#3488D1"
case .highlight:
return "#7685B3"
}
}

var darkColor: String {
switch self {
case .background:
return "#050505"
case .primary:
return "#EEEEEE"
case .secondary:
return "#3488D1"
case .highlight:
return "#7685B3"
}
}
}
}
@@ -0,0 +1,78 @@
//
// Book+AVFoundation.swift
// BookPlayer
//
// Created by Gianni Carlo on 4/23/19.
// Copyright © 2019 Tortuga Power. All rights reserved.
//
import AVFoundation
import BookPlayerKit
import CoreData
import Foundation

extension Book {
func setChapters(from asset: AVAsset, context: NSManagedObjectContext) {
for locale in asset.availableChapterLocales {
let chaptersMetadata = asset.chapterMetadataGroups(withTitleLocale: locale, containingItemsWithCommonKeys: [AVMetadataKey.commonKeyArtwork])

for (index, chapterMetadata) in chaptersMetadata.enumerated() {
let chapterIndex = index + 1
let chapter = Chapter(from: asset, context: context)

chapter.title = AVMetadataItem.metadataItems(from: chapterMetadata.items,
withKey: AVMetadataKey.commonKeyTitle,
keySpace: AVMetadataKeySpace.common).first?.value?.copy(with: nil) as? String ?? ""
chapter.start = CMTimeGetSeconds(chapterMetadata.timeRange.start)
chapter.duration = CMTimeGetSeconds(chapterMetadata.timeRange.duration)
chapter.index = Int16(chapterIndex)

self.addToChapters(chapter)
}
}

self.currentChapter = self.chapters?.array.first as? Chapter
}

convenience init(from bookUrl: FileItem, context: NSManagedObjectContext) {
let entity = NSEntityDescription.entity(forEntityName: "Book", in: context)!
self.init(entity: entity, insertInto: context)
let fileURL = bookUrl.processedUrl!
self.ext = fileURL.pathExtension
self.identifier = fileURL.lastPathComponent
let asset = AVAsset(url: fileURL)

let titleFromMeta = AVMetadataItem.metadataItems(from: asset.metadata, withKey: AVMetadataKey.commonKeyTitle, keySpace: AVMetadataKeySpace.common).first?.value?.copy(with: nil) as? String
let authorFromMeta = AVMetadataItem.metadataItems(from: asset.metadata, withKey: AVMetadataKey.commonKeyArtist, keySpace: AVMetadataKeySpace.common).first?.value?.copy(with: nil) as? String

self.title = titleFromMeta ?? bookUrl.originalUrl.lastPathComponent.replacingOccurrences(of: "_", with: " ")
self.author = authorFromMeta ?? "Unknown Author"
self.duration = CMTimeGetSeconds(asset.duration)
self.originalFileName = bookUrl.originalUrl.lastPathComponent
self.isFinished = false

var colors: Theme!
if let data = AVMetadataItem.metadataItems(from: asset.metadata, withKey: AVMetadataKey.commonKeyArtwork, keySpace: AVMetadataKeySpace.common).first?.value?.copy(with: nil) as? NSData {
self.artworkData = data
colors = Theme(from: self.artwork, context: context)
} else {
colors = Theme(context: context)
self.usesDefaultArtwork = true
}

colors.title = self.title

self.artworkColors = colors

self.setChapters(from: asset, context: context)

let legacyIdentifier = bookUrl.originalUrl.lastPathComponent
let storedTime = UserDefaults.standard.double(forKey: legacyIdentifier)

// migration of time
if storedTime > 0 {
self.currentTime = storedTime
UserDefaults.standard.removeObject(forKey: legacyIdentifier)
}
}
}
@@ -1,21 +1,17 @@
//
// Chapter+CoreDataClass.swift
// Chapter+AVFoundation.swift
// BookPlayer
//
// Created by Gianni Carlo on 5/9/18.
// Copyright © 2018 Tortuga Power. All rights reserved.
//
// Created by Gianni Carlo on 4/23/19.
// Copyright © 2019 Tortuga Power. All rights reserved.
//
import AVFoundation
import BookPlayerKit
import CoreData
import Foundation

public class Chapter: NSManagedObject {
var end: TimeInterval {
return start + duration
}

extension Chapter {
convenience init(from asset: AVAsset, context: NSManagedObjectContext) {
let entity = NSEntityDescription.entity(forEntityName: "Chapter", in: context)!
self.init(entity: entity, insertInto: context)
@@ -9,23 +9,23 @@
import UIKit

extension Notification.Name {
public static let processingFile = Notification.Name("com.tortugapower.audiobookplayer.file.process")
public static let newFileUrl = Notification.Name("com.tortugapower.audiobookplayer.file.new")
public static let importOperation = Notification.Name("com.tortugapower.audiobookplayer.operation.new")
public static let requestReview = Notification.Name("com.tortugapower.audiobookplayer.requestreview")
public static let updatePercentage = Notification.Name("com.tortugapower.audiobookplayer.book.percentage")
public static let chapterChange = Notification.Name("com.tortugapower.audiobookplayer.book.chapter")
public static let bookReady = Notification.Name("com.tortugapower.audiobookplayer.book.ready")
public static let bookPlayed = Notification.Name("com.tortugapower.audiobookplayer.book.play")
public static let bookPaused = Notification.Name("com.tortugapower.audiobookplayer.book.pause")
public static let bookStopped = Notification.Name("com.tortugapower.audiobookplayer.book.stop")
public static let bookEnd = Notification.Name("com.tortugapower.audiobookplayer.book.end")
public static let bookChange = Notification.Name("com.tortugapower.audiobookplayer.book.change")
public static let bookPlaying = Notification.Name("com.tortugapower.audiobookplayer.book.playback")
public static let skipIntervalsChange = Notification.Name("com.tortugapower.audiobookplayer.settings.skip")
public static let reloadData = Notification.Name("com.tortugapower.audiobookplayer.reloaddata")
public static let playerPresented = Notification.Name("com.tortugapower.audiobookplayer.player.presented")
public static let playerDismissed = Notification.Name("com.tortugapower.audiobookplayer.player.dismissed")
public static let themeChange = Notification.Name("com.tortugapower.audiobookplayer.theme.change")
public static let donationMade = Notification.Name("com.tortugapower.audiobookplayer.donation.made")
static let processingFile = Notification.Name("com.tortugapower.audiobookplayer.file.process")
static let newFileUrl = Notification.Name("com.tortugapower.audiobookplayer.file.new")
static let importOperation = Notification.Name("com.tortugapower.audiobookplayer.operation.new")
static let requestReview = Notification.Name("com.tortugapower.audiobookplayer.requestreview")
static let updatePercentage = Notification.Name("com.tortugapower.audiobookplayer.book.percentage")
static let chapterChange = Notification.Name("com.tortugapower.audiobookplayer.book.chapter")
static let bookReady = Notification.Name("com.tortugapower.audiobookplayer.book.ready")
static let bookPlayed = Notification.Name("com.tortugapower.audiobookplayer.book.play")
static let bookPaused = Notification.Name("com.tortugapower.audiobookplayer.book.pause")
static let bookStopped = Notification.Name("com.tortugapower.audiobookplayer.book.stop")
static let bookEnd = Notification.Name("com.tortugapower.audiobookplayer.book.end")
static let bookChange = Notification.Name("com.tortugapower.audiobookplayer.book.change")
static let bookPlaying = Notification.Name("com.tortugapower.audiobookplayer.book.playback")
static let skipIntervalsChange = Notification.Name("com.tortugapower.audiobookplayer.settings.skip")
static let reloadData = Notification.Name("com.tortugapower.audiobookplayer.reloaddata")
static let playerPresented = Notification.Name("com.tortugapower.audiobookplayer.player.presented")
static let playerDismissed = Notification.Name("com.tortugapower.audiobookplayer.player.dismissed")
static let themeChange = Notification.Name("com.tortugapower.audiobookplayer.theme.change")
static let donationMade = Notification.Name("com.tortugapower.audiobookplayer.donation.made")
}
@@ -6,6 +6,7 @@
// Copyright © 2019 Tortuga Power. All rights reserved.
//
import BookPlayerKit
import Foundation

extension UserDefaults {
@@ -6,6 +6,7 @@
// Copyright © 2018 Tortuga Power. All rights reserved.
//
import BookPlayerKit
import Themeable
import UIKit

@@ -6,6 +6,7 @@
// Copyright © 2018 Tortuga Power. All rights reserved.
//
import BookPlayerKit
import MarqueeLabelSwift
import Themeable
import UIKit
@@ -1,52 +1,25 @@
//
// DataManager.swift
// DataManager+BookPlayer.swift
// BookPlayer
//
// Created by Gianni Carlo on 5/30/17.
// Copyright © 2017 Tortuga Power. All rights reserved.
// Created by Gianni Carlo on 4/23/19.
// Copyright © 2019 Tortuga Power. All rights reserved.
//
import AVFoundation
import BookPlayerKit
import CoreData
import Foundation
import IDZSwiftCommonCrypto
import UIKit

class DataManager {
static let processedFolderName = "Processed"

extension DataManager {
static let importer = ImportManager()
static let queue = OperationQueue()

// MARK: - Folder URLs
class func getDocumentsFolderURL() -> URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}

class func getProcessedFolderURL() -> URL {
let documentsURL = self.getDocumentsFolderURL()

let processedFolderURL = documentsURL.appendingPathComponent(self.processedFolderName)

if !FileManager.default.fileExists(atPath: processedFolderURL.path) {
do {
try FileManager.default.createDirectory(at: processedFolderURL, withIntermediateDirectories: true, attributes: nil)
} catch {
fatalError("Couldn't create Processed folder")
}
}

return processedFolderURL
}

internal static var storeUrl: URL {
return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.ApplicationGroupIdentifier)!.appendingPathComponent("BookPlayer.sqlite")
}

// MARK: - Operations
class func start(_ operation: Operation) {
public class func start(_ operation: Operation) {
self.queue.addOperation(operation)
}

@@ -66,7 +39,7 @@ class DataManager {

// MARK: - Core Data stack
class func migrateStack() throws {
public class func migrateStack() throws {
let name = "BookPlayer"
let container = NSPersistentContainer(name: name)
let psc = container.persistentStoreCoordinator
@@ -92,7 +65,7 @@ class DataManager {
/**
Remove file protection for processed folder so that when the app is on the background and the iPhone is locked, autoplay still works
*/
class func makeFilesPublic() {
public class func makeFilesPublic() {
let processedFolder = self.getProcessedFolderURL()

guard let files = self.getFiles(from: processedFolder) else { return }
@@ -115,7 +88,7 @@ class DataManager {
- Parameter folder: The folder from which to get all the files urls
- Returns: Array of file-only `URL`, directories are excluded. It returns `nil` if the folder is empty.
*/
class func getFiles(from folder: URL) -> [URL]? {
public class func getFiles(from folder: URL) -> [URL]? {
// Get reference of all the files located inside the Documents folder
guard let urls = try? FileManager.default.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil, options: .skipsSubdirectoryDescendants) else {
return nil
@@ -135,7 +108,7 @@ class DataManager {
Notifies the ImportManager about the new file
- Parameter origin: File original location
*/
class func processFile(at origin: URL) {
public class func processFile(at origin: URL) {
self.processFile(at: origin, destinationFolder: self.getProcessedFolderURL())
}

@@ -151,7 +124,7 @@ class DataManager {
/**
Find all the files in the documents folder and send notifications about their existence.
*/
class func notifyPendingFiles() {
public class func notifyPendingFiles() {
let documentsFolder = self.getDocumentsFolderURL()

// Get reference of all the files located inside the folder
@@ -166,15 +139,15 @@ class DataManager {
}
}

class func exists(_ book: Book) -> Bool {
public class func exists(_ book: Book) -> Bool {
guard let fileURL = book.fileURL else { return false }

return FileManager.default.fileExists(atPath: fileURL.path)
}

// MARK: - Themes
class func setupDefaultTheme() {
public class func setupDefaultTheme() {
let library = self.getLibrary()

guard library.currentTheme == nil else { return }
@@ -191,7 +164,7 @@ class DataManager {
self.saveContext()
}

class func getLocalThemes() -> [Theme] {
public class func getLocalThemes() -> [Theme] {
guard
let themesFile = Bundle.main.url(forResource: "Themes", withExtension: "json"),
let data = try? Data(contentsOf: themesFile, options: .mappedIfSafe),
@@ -224,32 +197,20 @@ class DataManager {
return themes
}

class func getExtractedThemes() -> [Theme] {
public class func getExtractedThemes() -> [Theme] {
let library = self.getLibrary()
return library.extractedThemes?.array as? [Theme] ?? []
}

class func addExtractedTheme(_ theme: Theme) {
public class func addExtractedTheme(_ theme: Theme) {
let library = self.getLibrary()
library.addToExtractedThemes(theme)
self.saveContext()
}

class func setCurrentTheme(_ theme: Theme) {
public class func setCurrentTheme(_ theme: Theme) {
let library = self.getLibrary()
library.currentTheme = theme
DataManager.saveContext()
}

// MARK: - Icons
class func getIcons() -> [Icon] {
guard
let iconsFile = Bundle.main.url(forResource: "Icons", withExtension: "json"),
let data = try? Data(contentsOf: iconsFile, options: .mappedIfSafe),
let icons = try? JSONDecoder().decode([Icon].self, from: data)
else { return [] }

return icons
}
}

0 comments on commit 05f0f64

Please sign in to comment.
You can’t perform that action at this time.